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