]> git.ozlabs.org Git - patchwork/blob - patchwork/tests/test_bundles.py
tests: Move 'reverse' calls inside 'setUp'
[patchwork] / patchwork / tests / test_bundles.py
1 # Patchwork - automated patch tracking system
2 # Copyright (C) 2009 Jeremy Kerr <jk@ozlabs.org>
3 #
4 # This file is part of the Patchwork package.
5 #
6 # Patchwork is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # Patchwork is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with Patchwork; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19
20 import unittest
21 import datetime
22 from django.test import TestCase
23 from django.test.client import Client
24 from django.utils.http import urlencode
25 from django.conf import settings
26 from patchwork.models import Patch, Bundle, BundlePatch, Person
27 from patchwork.tests.utils import defaults, create_user, find_in_context
28
29 def bundle_url(bundle):
30     return '/bundle/%s/%s/' % (bundle.owner.username, bundle.name)
31
32 class BundleListTest(TestCase):
33     def setUp(self):
34         self.user = create_user()
35         self.client.login(username = self.user.username,
36                 password = self.user.username)
37
38     def testNoBundles(self):
39         response = self.client.get('/user/bundles/')
40         self.failUnlessEqual(response.status_code, 200)
41         self.failUnlessEqual(
42                 len(find_in_context(response.context, 'bundles')), 0)
43
44     def testSingleBundle(self):
45         defaults.project.save()
46         bundle = Bundle(owner = self.user, project = defaults.project)
47         bundle.save()
48         response = self.client.get('/user/bundles/')
49         self.failUnlessEqual(response.status_code, 200)
50         self.failUnlessEqual(
51                 len(find_in_context(response.context, 'bundles')), 1)
52
53     def tearDown(self):
54         self.user.delete()
55
56 class BundleTestBase(TestCase):
57     fixtures = ['default_states']
58     def setUp(self, patch_count=3):
59         patch_names = ['testpatch%d' % (i) for i in range(1, patch_count+1)]
60         self.user = create_user()
61         self.client.login(username = self.user.username,
62                 password = self.user.username)
63         defaults.project.save()
64         self.bundle = Bundle(owner = self.user, project = defaults.project,
65                 name = 'testbundle')
66         self.bundle.save()
67         self.patches = []
68
69         for patch_name in patch_names:
70             patch = Patch(project = defaults.project,
71                                msgid = patch_name, name = patch_name,
72                                submitter = Person.objects.get(user = self.user),
73                                content = '')
74             patch.save()
75             self.patches.append(patch)
76
77     def tearDown(self):
78         for patch in self.patches:
79             patch.delete()
80         self.bundle.delete()
81         self.user.delete()
82
83 class BundleViewTest(BundleTestBase):
84
85     def testEmptyBundle(self):
86         response = self.client.get(bundle_url(self.bundle))
87         self.failUnlessEqual(response.status_code, 200)
88         page = find_in_context(response.context, 'page')
89         self.failUnlessEqual(len(page.object_list), 0)
90
91     def testNonEmptyBundle(self):
92         self.bundle.append_patch(self.patches[0])
93
94         response = self.client.get(bundle_url(self.bundle))
95         self.failUnlessEqual(response.status_code, 200)
96         page = find_in_context(response.context, 'page')
97         self.failUnlessEqual(len(page.object_list), 1)
98
99     def testBundleOrder(self):
100         for patch in self.patches:
101             self.bundle.append_patch(patch)
102
103         response = self.client.get(bundle_url(self.bundle))
104
105         pos = 0
106         for patch in self.patches:
107             next_pos = response.content.find(patch.name)
108             # ensure that this patch is after the previous
109             self.failUnless(next_pos > pos)
110             pos = next_pos
111
112         # reorder and recheck
113         i = 0
114         for patch in self.patches.__reversed__():
115             bundlepatch = BundlePatch.objects.get(bundle = self.bundle,
116                     patch = patch)
117             bundlepatch.order = i
118             bundlepatch.save()
119             i += 1
120
121         response = self.client.get(bundle_url(self.bundle))
122         pos = len(response.content)
123         for patch in self.patches:
124             next_pos = response.content.find(patch.name)
125             # ensure that this patch is now *before* the previous
126             self.failUnless(next_pos < pos)
127             pos = next_pos
128
129 class BundleUpdateTest(BundleTestBase):
130
131     def setUp(self):
132         super(BundleUpdateTest, self).setUp()
133         self.newname = 'newbundlename'
134
135     def checkPatchformErrors(self, response):
136         formname = 'patchform'
137         if not formname in response.context:
138             return
139         form = response.context[formname]
140         if not form:
141             return
142         self.assertEquals(form.errors, {})
143
144     def publicString(self, public):
145         if public:
146             return 'on'
147         return ''
148
149     def testNoAction(self):
150         data = {
151             'form': 'bundle',
152             'name': self.newname,
153             'public': self.publicString(not self.bundle.public)
154         }
155         response = self.client.post(bundle_url(self.bundle), data)
156         self.assertEqual(response.status_code, 200)
157
158         bundle = Bundle.objects.get(pk = self.bundle.pk)
159         self.assertEqual(bundle.name, self.bundle.name)
160         self.assertEqual(bundle.public, self.bundle.public)
161
162     def testUpdateName(self):
163         newname = 'newbundlename'
164         data = {
165             'form': 'bundle',
166             'action': 'update',
167             'name': newname,
168             'public': self.publicString(self.bundle.public)
169         }
170         response = self.client.post(bundle_url(self.bundle), data)
171         bundle = Bundle.objects.get(pk = self.bundle.pk)
172         self.assertRedirects(response, bundle_url(bundle))
173         self.assertEqual(bundle.name, newname)
174         self.assertEqual(bundle.public, self.bundle.public)
175
176     def testUpdatePublic(self):
177         newname = 'newbundlename'
178         data = {
179             'form': 'bundle',
180             'action': 'update',
181             'name': self.bundle.name,
182             'public': self.publicString(not self.bundle.public)
183         }
184         response = self.client.post(bundle_url(self.bundle), data)
185         self.assertEqual(response.status_code, 200)
186         bundle = Bundle.objects.get(pk = self.bundle.pk)
187         self.assertEqual(bundle.name, self.bundle.name)
188         self.assertEqual(bundle.public, not self.bundle.public)
189
190         # check other forms for errors
191         self.checkPatchformErrors(response)
192
193 class BundleMaintainerUpdateTest(BundleUpdateTest):
194
195     def setUp(self):
196         super(BundleMaintainerUpdateTest, self).setUp()
197         profile = self.user.profile
198         profile.maintainer_projects.add(defaults.project)
199         profile.save()
200
201 class BundlePublicViewTest(BundleTestBase):
202
203     def setUp(self):
204         super(BundlePublicViewTest, self).setUp()
205         self.client.logout()
206         self.bundle.append_patch(self.patches[0])
207         self.url = bundle_url(self.bundle)
208
209     def testPublicBundle(self):
210         self.bundle.public = True
211         self.bundle.save()
212         response = self.client.get(self.url)
213         self.assertEqual(response.status_code, 200)
214         self.assertContains(response, self.patches[0].name)
215
216     def testPrivateBundle(self):
217         self.bundle.public = False
218         self.bundle.save()
219         response = self.client.get(self.url)
220         self.assertEqual(response.status_code, 404)
221
222 class BundlePublicViewMboxTest(BundlePublicViewTest):
223     def setUp(self):
224         super(BundlePublicViewMboxTest, self).setUp()
225         self.url = bundle_url(self.bundle) + "mbox/"
226
227 class BundlePublicModifyTest(BundleTestBase):
228     """Ensure that non-owners can't modify bundles"""
229
230     def setUp(self):
231         super(BundlePublicModifyTest, self).setUp()
232         self.bundle.public = True
233         self.bundle.save()
234         self.other_user = create_user()
235
236     def testBundleFormPresence(self):
237         """Check for presence of the modify form on the bundle"""
238         self.client.login(username = self.other_user.username,
239                 password = self.other_user.username)
240         response = self.client.get(bundle_url(self.bundle))
241         self.assertNotContains(response, 'name="form" value="bundle"')
242         self.assertNotContains(response, 'Change order')
243
244     def testBundleFormSubmission(self):
245         oldname = 'oldbundlename'
246         newname = 'newbundlename'
247         data = {
248             'form': 'bundle',
249             'action': 'update',
250             'name': newname,
251         }
252         self.bundle.name = oldname
253         self.bundle.save()
254
255         # first, check that we can modify with the owner
256         self.client.login(username = self.user.username,
257                 password = self.user.username)
258         response = self.client.post(bundle_url(self.bundle), data)
259         self.bundle = Bundle.objects.get(pk = self.bundle.pk)
260         self.assertEqual(self.bundle.name, newname)
261
262         # reset bundle name
263         self.bundle.name = oldname
264         self.bundle.save()
265
266         # log in with a different user, and check that we can no longer modify
267         self.client.login(username = self.other_user.username,
268                 password = self.other_user.username)
269         response = self.client.post(bundle_url(self.bundle), data)
270         self.bundle = Bundle.objects.get(pk = self.bundle.pk)
271         self.assertNotEqual(self.bundle.name, newname)
272
273 class BundleCreateFromListTest(BundleTestBase):
274     def testCreateEmptyBundle(self):
275         newbundlename = 'testbundle-new'
276         params = {'form': 'patchlistform',
277                   'bundle_name': newbundlename,
278                   'action': 'Create',
279                   'project': defaults.project.id}
280
281         response = self.client.post(
282                 '/project/%s/list/' % defaults.project.linkname,
283                 params)
284
285         self.assertContains(response, 'Bundle %s created' % newbundlename)
286
287     def testCreateNonEmptyBundle(self):
288         newbundlename = 'testbundle-new'
289         patch = self.patches[0]
290
291         params = {'form': 'patchlistform',
292                   'bundle_name': newbundlename,
293                   'action': 'Create',
294                   'project': defaults.project.id,
295                   'patch_id:%d' % patch.id: 'checked'}
296
297         response = self.client.post(
298                 '/project/%s/list/' % defaults.project.linkname,
299                 params)
300
301         self.assertContains(response, 'Bundle %s created' % newbundlename)
302         self.assertContains(response, 'added to bundle %s' % newbundlename,
303             count = 1)
304
305         bundle = Bundle.objects.get(name = newbundlename)
306         self.failUnlessEqual(bundle.patches.count(), 1)
307         self.failUnlessEqual(bundle.patches.all()[0], patch)
308
309     def testCreateNonEmptyBundleEmptyName(self):
310         newbundlename = 'testbundle-new'
311         patch = self.patches[0]
312
313         n_bundles = Bundle.objects.count()
314
315         params = {'form': 'patchlistform',
316                   'bundle_name': '',
317                   'action': 'Create',
318                   'project': defaults.project.id,
319                   'patch_id:%d' % patch.id: 'checked'}
320
321         response = self.client.post(
322                 '/project/%s/list/' % defaults.project.linkname,
323                 params)
324
325         self.assertContains(response, 'No bundle name was specified',
326                 status_code = 200)
327
328         # test that no new bundles are present
329         self.failUnlessEqual(n_bundles, Bundle.objects.count())
330
331     def testCreateDuplicateName(self):
332         newbundlename = 'testbundle-dup'
333         patch = self.patches[0]
334
335         params = {'form': 'patchlistform',
336                   'bundle_name': newbundlename,
337                   'action': 'Create',
338                   'project': defaults.project.id,
339                   'patch_id:%d' % patch.id: 'checked'}
340
341         response = self.client.post(
342                 '/project/%s/list/' % defaults.project.linkname,
343                 params)
344
345         n_bundles = Bundle.objects.count()
346         self.assertContains(response, 'Bundle %s created' % newbundlename)
347         self.assertContains(response, 'added to bundle %s' % newbundlename,
348             count = 1)
349
350         bundle = Bundle.objects.get(name = newbundlename)
351         self.failUnlessEqual(bundle.patches.count(), 1)
352         self.failUnlessEqual(bundle.patches.all()[0], patch)
353
354         response = self.client.post(
355                 '/project/%s/list/' % defaults.project.linkname,
356                 params)
357
358         self.assertNotContains(response, 'Bundle %s created' % newbundlename)
359         self.assertContains(response, 'You already have a bundle called')
360         self.assertEqual(Bundle.objects.count(), n_bundles)
361         self.assertEqual(bundle.patches.count(), 1)
362
363 class BundleCreateFromPatchTest(BundleTestBase):
364     def testCreateNonEmptyBundle(self):
365         newbundlename = 'testbundle-new'
366         patch = self.patches[0]
367
368         params = {'name': newbundlename,
369                   'action': 'createbundle'}
370
371         response = self.client.post('/patch/%d/' % patch.id, params)
372
373         self.assertContains(response,
374                 'Bundle %s created' % newbundlename)
375
376         bundle = Bundle.objects.get(name = newbundlename)
377         self.failUnlessEqual(bundle.patches.count(), 1)
378         self.failUnlessEqual(bundle.patches.all()[0], patch)
379
380     def testCreateWithExistingName(self):
381         newbundlename = self.bundle.name
382         patch = self.patches[0]
383
384         params = {'name': newbundlename,
385                   'action': 'createbundle'}
386
387         response = self.client.post('/patch/%d/' % patch.id, params)
388
389         self.assertContains(response,
390                 'A bundle called %s already exists' % newbundlename)
391
392         count = Bundle.objects.count()
393         self.failUnlessEqual(Bundle.objects.count(), 1)
394
395 class BundleAddFromListTest(BundleTestBase):
396     def testAddToEmptyBundle(self):
397         patch = self.patches[0]
398         params = {'form': 'patchlistform',
399                   'action': 'Add',
400                   'project': defaults.project.id,
401                   'bundle_id': self.bundle.id,
402                   'patch_id:%d' % patch.id: 'checked'}
403
404         response = self.client.post(
405                 '/project/%s/list/' % defaults.project.linkname,
406                 params)
407
408         self.assertContains(response, 'added to bundle %s' % self.bundle.name,
409             count = 1)
410
411         self.failUnlessEqual(self.bundle.patches.count(), 1)
412         self.failUnlessEqual(self.bundle.patches.all()[0], patch)
413
414     def testAddToNonEmptyBundle(self):
415         self.bundle.append_patch(self.patches[0])
416         patch = self.patches[1]
417         params = {'form': 'patchlistform',
418                   'action': 'Add',
419                   'project': defaults.project.id,
420                   'bundle_id': self.bundle.id,
421                   'patch_id:%d' % patch.id: 'checked'}
422
423         response = self.client.post(
424                 '/project/%s/list/' % defaults.project.linkname,
425                 params)
426
427         self.assertContains(response, 'added to bundle %s' % self.bundle.name,
428             count = 1)
429
430         self.failUnlessEqual(self.bundle.patches.count(), 2)
431         self.failUnless(self.patches[0] in self.bundle.patches.all())
432         self.failUnless(self.patches[1] in self.bundle.patches.all())
433
434         # check order
435         bps = [ BundlePatch.objects.get(bundle = self.bundle,
436                                         patch = self.patches[i]) \
437                 for i in [0, 1] ]
438         self.failUnless(bps[0].order < bps[1].order)
439
440     def testAddDuplicate(self):
441         self.bundle.append_patch(self.patches[0])
442         count = self.bundle.patches.count()
443         patch = self.patches[0]
444
445         params = {'form': 'patchlistform',
446                   'action': 'Add',
447                   'project': defaults.project.id,
448                   'bundle_id': self.bundle.id,
449                   'patch_id:%d' % patch.id: 'checked'}
450
451         response = self.client.post(
452                 '/project/%s/list/' % defaults.project.linkname,
453                 params)
454
455         self.assertContains(response, 'Patch &#39;%s&#39; already in bundle' \
456                             % patch.name, count = 1, status_code = 200)
457
458         self.assertEquals(count, self.bundle.patches.count())
459
460     def testAddNewAndDuplicate(self):
461         self.bundle.append_patch(self.patches[0])
462         count = self.bundle.patches.count()
463         patch = self.patches[0]
464
465         params = {'form': 'patchlistform',
466                   'action': 'Add',
467                   'project': defaults.project.id,
468                   'bundle_id': self.bundle.id,
469                   'patch_id:%d' % patch.id: 'checked',
470                   'patch_id:%d' % self.patches[1].id: 'checked'}
471
472         response = self.client.post(
473                 '/project/%s/list/' % defaults.project.linkname,
474                 params)
475
476         self.assertContains(response, 'Patch &#39;%s&#39; already in bundle' \
477                             % patch.name, count = 1, status_code = 200)
478         self.assertContains(response, 'Patch &#39;%s&#39; added to bundle' \
479                             % self.patches[1].name, count = 1,
480                             status_code = 200)
481         self.assertEquals(count + 1, self.bundle.patches.count())
482
483 class BundleAddFromPatchTest(BundleTestBase):
484     def testAddToEmptyBundle(self):
485         patch = self.patches[0]
486         params = {'action': 'addtobundle',
487                   'bundle_id': self.bundle.id}
488
489         response = self.client.post('/patch/%d/' % patch.id, params)
490
491         self.assertContains(response,
492                 'added to bundle &quot;%s&quot;' % self.bundle.name,
493                 count = 1)
494
495         self.failUnlessEqual(self.bundle.patches.count(), 1)
496         self.failUnlessEqual(self.bundle.patches.all()[0], patch)
497
498     def testAddToNonEmptyBundle(self):
499         self.bundle.append_patch(self.patches[0])
500         patch = self.patches[1]
501         params = {'action': 'addtobundle',
502                   'bundle_id': self.bundle.id}
503
504         response = self.client.post('/patch/%d/' % patch.id, params)
505
506         self.assertContains(response,
507                 'added to bundle &quot;%s&quot;' % self.bundle.name,
508                 count = 1)
509
510         self.failUnlessEqual(self.bundle.patches.count(), 2)
511         self.failUnless(self.patches[0] in self.bundle.patches.all())
512         self.failUnless(self.patches[1] in self.bundle.patches.all())
513
514         # check order
515         bps = [ BundlePatch.objects.get(bundle = self.bundle,
516                                         patch = self.patches[i]) \
517                 for i in [0, 1] ]
518         self.failUnless(bps[0].order < bps[1].order)
519
520 class BundleInitialOrderTest(BundleTestBase):
521     """When creating bundles from a patch list, ensure that the patches in the
522        bundle are ordered by date"""
523
524     def setUp(self):
525         super(BundleInitialOrderTest, self).setUp(5)
526
527         # put patches in an arbitrary order
528         idxs = [2, 4, 3, 1, 0]
529         self.patches = [ self.patches[i] for i in idxs ]
530
531         # set dates to be sequential
532         last_patch = self.patches[0]
533         for patch in self.patches[1:]:
534             patch.date = last_patch.date + datetime.timedelta(0, 1)
535             patch.save()
536             last_patch = patch
537
538     def _testOrder(self, ids, expected_order):
539         newbundlename = 'testbundle-new'
540
541         # need to define our querystring explicity to enforce ordering
542         params = {'form': 'patchlistform',
543                   'bundle_name': newbundlename,
544                   'action': 'Create',
545                   'project': defaults.project.id,
546         }
547
548         data = urlencode(params) + \
549                ''.join([ '&patch_id:%d=checked' % i for i in ids ])
550
551         response = self.client.post(
552                 '/project/%s/list/' % defaults.project.linkname,
553                 data = data,
554                 content_type = 'application/x-www-form-urlencoded',
555                 )
556
557         self.assertContains(response, 'Bundle %s created' % newbundlename)
558         self.assertContains(response, 'added to bundle %s' % newbundlename,
559             count = 5)
560
561         bundle = Bundle.objects.get(name = newbundlename)
562
563         # BundlePatches should be sorted by .order by default
564         bps = BundlePatch.objects.filter(bundle = bundle)
565
566         for (bp, p) in zip(bps, expected_order):
567             self.assertEqual(bp.patch.pk, p.pk)
568
569         bundle.delete()
570
571     def testBundleForwardOrder(self):
572         ids = map(lambda p: p.id, self.patches)
573         self._testOrder(ids, self.patches)
574
575     def testBundleReverseOrder(self):
576         ids = map(lambda p: p.id, self.patches)
577         ids.reverse()
578         self._testOrder(ids, self.patches)
579
580 class BundleReorderTest(BundleTestBase):
581     def setUp(self):
582         super(BundleReorderTest, self).setUp(5)
583         for i in range(5):
584             self.bundle.append_patch(self.patches[i])
585
586     def checkReordering(self, neworder, start, end):
587         neworder_ids = [ self.patches[i].id for i in neworder ]
588
589         firstpatch = BundlePatch.objects.get(bundle = self.bundle,
590                 patch = self.patches[start]).patch
591
592         slice_ids = neworder_ids[start:end]
593         params = {'form': 'reorderform',
594                   'order_start': firstpatch.id,
595                   'neworder': slice_ids}
596
597         response = self.client.post(bundle_url(self.bundle), params)
598
599         self.failUnlessEqual(response.status_code, 200)
600
601         bps = BundlePatch.objects.filter(bundle = self.bundle) \
602                         .order_by('order')
603
604         # check if patch IDs are in the expected order:
605         bundle_ids = [ bp.patch.id for bp in bps ]
606         self.failUnlessEqual(neworder_ids, bundle_ids)
607
608         # check if order field is still sequential:
609         order_numbers = [ bp.order for bp in bps ]
610         expected_order = range(1, len(neworder)+1) # [1 ... len(neworder)]
611         self.failUnlessEqual(order_numbers, expected_order)
612
613     def testBundleReorderAll(self):
614         # reorder all patches:
615         self.checkReordering([2,1,4,0,3], 0, 5)
616
617     def testBundleReorderEnd(self):
618         # reorder only the last three patches
619         self.checkReordering([0,1,3,2,4], 2, 5)
620
621     def testBundleReorderBegin(self):
622         # reorder only the first three patches
623         self.checkReordering([2,0,1,3,4], 0, 3)
624
625     def testBundleReorderMiddle(self):
626         # reorder only 2nd, 3rd, and 4th patches
627         self.checkReordering([0,2,3,1,4], 1, 4)
628
629 class BundleRedirTest(BundleTestBase):
630     # old URL: private bundles used to be under /user/bundle/<id>
631
632     def setUp(self):
633         super(BundleRedirTest, self).setUp()
634
635     @unittest.skipIf(not settings.COMPAT_REDIR, "compat redirections disabled")
636     def testBundleRedir(self):
637         url = '/user/bundle/%d/' % self.bundle.id
638         response = self.client.get(url)
639         self.assertRedirects(response, bundle_url(self.bundle))
640
641     @unittest.skipIf(not settings.COMPAT_REDIR, "compat redirections disabled")
642     def testMboxRedir(self):
643         url = '/user/bundle/%d/mbox/' % self.bundle.id
644         response = self.client.get(url)
645         self.assertRedirects(response,'/bundle/%s/%s/mbox/' %
646                                         (self.bundle.owner.username,
647                                          self.bundle.name))