]> git.ozlabs.org Git - patchwork/blob - apps/patchwork/tests/bundles.py
bundles: Remove separate public bundle views
[patchwork] / apps / patchwork / tests / 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.get_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
242     def testBundleFormSubmission(self):
243         oldname = 'oldbundlename'
244         newname = 'newbundlename'
245         data = {
246             'form': 'bundle',
247             'action': 'update',
248             'name': newname,
249         }
250         self.bundle.name = oldname
251         self.bundle.save()
252
253         # first, check that we can modify with the owner
254         self.client.login(username = self.user.username,
255                 password = self.user.username)
256         response = self.client.post(bundle_url(self.bundle), data)
257         self.bundle = Bundle.objects.get(pk = self.bundle.pk)
258         self.assertEqual(self.bundle.name, newname)
259
260         # reset bundle name
261         self.bundle.name = oldname
262         self.bundle.save()
263
264         # log in with a different user, and check that we can no longer modify
265         self.client.login(username = self.other_user.username,
266                 password = self.other_user.username)
267         response = self.client.post(bundle_url(self.bundle), data)
268         self.bundle = Bundle.objects.get(pk = self.bundle.pk)
269         self.assertNotEqual(self.bundle.name, newname)
270
271 class BundleCreateFromListTest(BundleTestBase):
272     def testCreateEmptyBundle(self):
273         newbundlename = 'testbundle-new'
274         params = {'form': 'patchlistform',
275                   'bundle_name': newbundlename,
276                   'action': 'Create',
277                   'project': defaults.project.id}
278
279         response = self.client.post(
280                 '/project/%s/list/' % defaults.project.linkname,
281                 params)
282
283         self.assertContains(response, 'Bundle %s created' % newbundlename)
284
285     def testCreateNonEmptyBundle(self):
286         newbundlename = 'testbundle-new'
287         patch = self.patches[0]
288
289         params = {'form': 'patchlistform',
290                   'bundle_name': newbundlename,
291                   'action': 'Create',
292                   'project': defaults.project.id,
293                   'patch_id:%d' % patch.id: 'checked'}
294
295         response = self.client.post(
296                 '/project/%s/list/' % defaults.project.linkname,
297                 params)
298
299         self.assertContains(response, 'Bundle %s created' % newbundlename)
300         self.assertContains(response, 'added to bundle %s' % newbundlename,
301             count = 1)
302
303         bundle = Bundle.objects.get(name = newbundlename)
304         self.failUnlessEqual(bundle.patches.count(), 1)
305         self.failUnlessEqual(bundle.patches.all()[0], patch)
306
307     def testCreateNonEmptyBundleEmptyName(self):
308         newbundlename = 'testbundle-new'
309         patch = self.patches[0]
310
311         n_bundles = Bundle.objects.count()
312
313         params = {'form': 'patchlistform',
314                   'bundle_name': '',
315                   'action': 'Create',
316                   'project': defaults.project.id,
317                   'patch_id:%d' % patch.id: 'checked'}
318
319         response = self.client.post(
320                 '/project/%s/list/' % defaults.project.linkname,
321                 params)
322
323         self.assertContains(response, 'No bundle name was specified',
324                 status_code = 200)
325
326         # test that no new bundles are present
327         self.failUnlessEqual(n_bundles, Bundle.objects.count())
328
329     def testCreateDuplicateName(self):
330         newbundlename = 'testbundle-dup'
331         patch = self.patches[0]
332
333         params = {'form': 'patchlistform',
334                   'bundle_name': newbundlename,
335                   'action': 'Create',
336                   'project': defaults.project.id,
337                   'patch_id:%d' % patch.id: 'checked'}
338
339         response = self.client.post(
340                 '/project/%s/list/' % defaults.project.linkname,
341                 params)
342
343         n_bundles = Bundle.objects.count()
344         self.assertContains(response, 'Bundle %s created' % newbundlename)
345         self.assertContains(response, 'added to bundle %s' % newbundlename,
346             count = 1)
347
348         bundle = Bundle.objects.get(name = newbundlename)
349         self.failUnlessEqual(bundle.patches.count(), 1)
350         self.failUnlessEqual(bundle.patches.all()[0], patch)
351
352         response = self.client.post(
353                 '/project/%s/list/' % defaults.project.linkname,
354                 params)
355
356         self.assertNotContains(response, 'Bundle %s created' % newbundlename)
357         self.assertContains(response, 'You already have a bundle called')
358         self.assertEqual(Bundle.objects.count(), n_bundles)
359         self.assertEqual(bundle.patches.count(), 1)
360
361 class BundleCreateFromPatchTest(BundleTestBase):
362     def testCreateNonEmptyBundle(self):
363         newbundlename = 'testbundle-new'
364         patch = self.patches[0]
365
366         params = {'name': newbundlename,
367                   'action': 'createbundle'}
368
369         response = self.client.post('/patch/%d/' % patch.id, params)
370
371         self.assertContains(response,
372                 'Bundle %s created' % newbundlename)
373
374         bundle = Bundle.objects.get(name = newbundlename)
375         self.failUnlessEqual(bundle.patches.count(), 1)
376         self.failUnlessEqual(bundle.patches.all()[0], patch)
377
378     def testCreateWithExistingName(self):
379         newbundlename = self.bundle.name
380         patch = self.patches[0]
381
382         params = {'name': newbundlename,
383                   'action': 'createbundle'}
384
385         response = self.client.post('/patch/%d/' % patch.id, params)
386
387         self.assertContains(response,
388                 'A bundle called %s already exists' % newbundlename)
389
390         count = Bundle.objects.count()
391         self.failUnlessEqual(Bundle.objects.count(), 1)
392
393 class BundleAddFromListTest(BundleTestBase):
394     def testAddToEmptyBundle(self):
395         patch = self.patches[0]
396         params = {'form': 'patchlistform',
397                   'action': 'Add',
398                   'project': defaults.project.id,
399                   'bundle_id': self.bundle.id,
400                   'patch_id:%d' % patch.id: 'checked'}
401
402         response = self.client.post(
403                 '/project/%s/list/' % defaults.project.linkname,
404                 params)
405
406         self.assertContains(response, 'added to bundle %s' % self.bundle.name,
407             count = 1)
408
409         self.failUnlessEqual(self.bundle.patches.count(), 1)
410         self.failUnlessEqual(self.bundle.patches.all()[0], patch)
411
412     def testAddToNonEmptyBundle(self):
413         self.bundle.append_patch(self.patches[0])
414         patch = self.patches[1]
415         params = {'form': 'patchlistform',
416                   'action': 'Add',
417                   'project': defaults.project.id,
418                   'bundle_id': self.bundle.id,
419                   'patch_id:%d' % patch.id: 'checked'}
420
421         response = self.client.post(
422                 '/project/%s/list/' % defaults.project.linkname,
423                 params)
424
425         self.assertContains(response, 'added to bundle %s' % self.bundle.name,
426             count = 1)
427
428         self.failUnlessEqual(self.bundle.patches.count(), 2)
429         self.failUnless(self.patches[0] in self.bundle.patches.all())
430         self.failUnless(self.patches[1] in self.bundle.patches.all())
431
432         # check order
433         bps = [ BundlePatch.objects.get(bundle = self.bundle,
434                                         patch = self.patches[i]) \
435                 for i in [0, 1] ]
436         self.failUnless(bps[0].order < bps[1].order)
437
438     def testAddDuplicate(self):
439         self.bundle.append_patch(self.patches[0])
440         count = self.bundle.patches.count()
441         patch = self.patches[0]
442
443         params = {'form': 'patchlistform',
444                   'action': 'Add',
445                   'project': defaults.project.id,
446                   'bundle_id': self.bundle.id,
447                   'patch_id:%d' % patch.id: 'checked'}
448
449         response = self.client.post(
450                 '/project/%s/list/' % defaults.project.linkname,
451                 params)
452
453         self.assertContains(response, 'Patch &#39;%s&#39; already in bundle' \
454                             % patch.name, count = 1, status_code = 200)
455
456         self.assertEquals(count, self.bundle.patches.count())
457
458     def testAddNewAndDuplicate(self):
459         self.bundle.append_patch(self.patches[0])
460         count = self.bundle.patches.count()
461         patch = self.patches[0]
462
463         params = {'form': 'patchlistform',
464                   'action': 'Add',
465                   'project': defaults.project.id,
466                   'bundle_id': self.bundle.id,
467                   'patch_id:%d' % patch.id: 'checked',
468                   'patch_id:%d' % self.patches[1].id: 'checked'}
469
470         response = self.client.post(
471                 '/project/%s/list/' % defaults.project.linkname,
472                 params)
473
474         self.assertContains(response, 'Patch &#39;%s&#39; already in bundle' \
475                             % patch.name, count = 1, status_code = 200)
476         self.assertContains(response, 'Patch &#39;%s&#39; added to bundle' \
477                             % self.patches[1].name, count = 1,
478                             status_code = 200)
479         self.assertEquals(count + 1, self.bundle.patches.count())
480
481 class BundleAddFromPatchTest(BundleTestBase):
482     def testAddToEmptyBundle(self):
483         patch = self.patches[0]
484         params = {'action': 'addtobundle',
485                   'bundle_id': self.bundle.id}
486
487         response = self.client.post('/patch/%d/' % patch.id, params)
488
489         self.assertContains(response,
490                 'added to bundle &quot;%s&quot;' % self.bundle.name,
491                 count = 1)
492
493         self.failUnlessEqual(self.bundle.patches.count(), 1)
494         self.failUnlessEqual(self.bundle.patches.all()[0], patch)
495
496     def testAddToNonEmptyBundle(self):
497         self.bundle.append_patch(self.patches[0])
498         patch = self.patches[1]
499         params = {'action': 'addtobundle',
500                   'bundle_id': self.bundle.id}
501
502         response = self.client.post('/patch/%d/' % patch.id, params)
503
504         self.assertContains(response,
505                 'added to bundle &quot;%s&quot;' % self.bundle.name,
506                 count = 1)
507
508         self.failUnlessEqual(self.bundle.patches.count(), 2)
509         self.failUnless(self.patches[0] in self.bundle.patches.all())
510         self.failUnless(self.patches[1] in self.bundle.patches.all())
511
512         # check order
513         bps = [ BundlePatch.objects.get(bundle = self.bundle,
514                                         patch = self.patches[i]) \
515                 for i in [0, 1] ]
516         self.failUnless(bps[0].order < bps[1].order)
517
518 class BundleInitialOrderTest(BundleTestBase):
519     """When creating bundles from a patch list, ensure that the patches in the
520        bundle are ordered by date"""
521
522     def setUp(self):
523         super(BundleInitialOrderTest, self).setUp(5)
524
525         # put patches in an arbitrary order
526         idxs = [2, 4, 3, 1, 0]
527         self.patches = [ self.patches[i] for i in idxs ]
528
529         # set dates to be sequential
530         last_patch = self.patches[0]
531         for patch in self.patches[1:]:
532             patch.date = last_patch.date + datetime.timedelta(0, 1)
533             patch.save()
534             last_patch = patch
535
536     def _testOrder(self, ids, expected_order):
537         newbundlename = 'testbundle-new'
538
539         # need to define our querystring explicity to enforce ordering
540         params = {'form': 'patchlistform',
541                   'bundle_name': newbundlename,
542                   'action': 'Create',
543                   'project': defaults.project.id,
544         }
545
546         data = urlencode(params) + \
547                ''.join([ '&patch_id:%d=checked' % i for i in ids ])
548
549         response = self.client.post(
550                 '/project/%s/list/' % defaults.project.linkname,
551                 data = data,
552                 content_type = 'application/x-www-form-urlencoded',
553                 )
554
555         self.assertContains(response, 'Bundle %s created' % newbundlename)
556         self.assertContains(response, 'added to bundle %s' % newbundlename,
557             count = 5)
558
559         bundle = Bundle.objects.get(name = newbundlename)
560
561         # BundlePatches should be sorted by .order by default
562         bps = BundlePatch.objects.filter(bundle = bundle)
563
564         for (bp, p) in zip(bps, expected_order):
565             self.assertEqual(bp.patch.pk, p.pk)
566
567         bundle.delete()
568
569     def testBundleForwardOrder(self):
570         ids = map(lambda p: p.id, self.patches)
571         self._testOrder(ids, self.patches)
572
573     def testBundleReverseOrder(self):
574         ids = map(lambda p: p.id, self.patches)
575         ids.reverse()
576         self._testOrder(ids, self.patches)
577
578 class BundleReorderTest(BundleTestBase):
579     def setUp(self):
580         super(BundleReorderTest, self).setUp(5)
581         for i in range(5):
582             self.bundle.append_patch(self.patches[i])
583
584     def checkReordering(self, neworder, start, end):
585         neworder_ids = [ self.patches[i].id for i in neworder ]
586
587         firstpatch = BundlePatch.objects.get(bundle = self.bundle,
588                 patch = self.patches[start]).patch
589
590         slice_ids = neworder_ids[start:end]
591         params = {'form': 'reorderform',
592                   'order_start': firstpatch.id,
593                   'neworder': slice_ids}
594
595         response = self.client.post(bundle_url(self.bundle), params)
596
597         self.failUnlessEqual(response.status_code, 200)
598
599         bps = BundlePatch.objects.filter(bundle = self.bundle) \
600                         .order_by('order')
601
602         # check if patch IDs are in the expected order:
603         bundle_ids = [ bp.patch.id for bp in bps ]
604         self.failUnlessEqual(neworder_ids, bundle_ids)
605
606         # check if order field is still sequential:
607         order_numbers = [ bp.order for bp in bps ]
608         expected_order = range(1, len(neworder)+1) # [1 ... len(neworder)]
609         self.failUnlessEqual(order_numbers, expected_order)
610
611     def testBundleReorderAll(self):
612         # reorder all patches:
613         self.checkReordering([2,1,4,0,3], 0, 5)
614
615     def testBundleReorderEnd(self):
616         # reorder only the last three patches
617         self.checkReordering([0,1,3,2,4], 2, 5)
618
619     def testBundleReorderBegin(self):
620         # reorder only the first three patches
621         self.checkReordering([2,0,1,3,4], 0, 3)
622
623     def testBundleReorderMiddle(self):
624         # reorder only 2nd, 3rd, and 4th patches
625         self.checkReordering([0,2,3,1,4], 1, 4)
626
627 class BundleRedirTest(BundleTestBase):
628     # old URL: private bundles used to be under /user/bundle/<id>
629
630     def setUp(self):
631         super(BundleRedirTest, self).setUp()
632
633     @unittest.skipIf(not settings.COMPAT_REDIR, "compat redirections disabled")
634     def testBundleRedir(self):
635         url = '/user/bundle/%d/' % self.bundle.id
636         response = self.client.get(url)
637         self.assertRedirects(response, bundle_url(self.bundle))
638
639     @unittest.skipIf(not settings.COMPAT_REDIR, "compat redirections disabled")
640     def testMboxRedir(self):
641         url = '/user/bundle/%d/mbox/' % self.bundle.id
642         response = self.client.get(url)
643         self.assertRedirects(response,'/bundle/%s/%s/mbox/' %
644                                         (self.bundle.owner.username,
645                                          self.bundle.name))