1 # Patchwork - automated patch tracking system
2 # Copyright (C) 2009 Jeremy Kerr <jk@ozlabs.org>
4 # This file is part of the Patchwork package.
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.
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.
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
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
29 def bundle_url(bundle):
30 return '/bundle/%s/%s/' % (bundle.owner.username, bundle.name)
32 class BundleListTest(TestCase):
34 self.user = create_user()
35 self.client.login(username = self.user.username,
36 password = self.user.username)
38 def testNoBundles(self):
39 response = self.client.get('/user/bundles/')
40 self.failUnlessEqual(response.status_code, 200)
42 len(find_in_context(response.context, 'bundles')), 0)
44 def testSingleBundle(self):
45 defaults.project.save()
46 bundle = Bundle(owner = self.user, project = defaults.project)
48 response = self.client.get('/user/bundles/')
49 self.failUnlessEqual(response.status_code, 200)
51 len(find_in_context(response.context, 'bundles')), 1)
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,
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),
74 self.patches.append(patch)
77 for patch in self.patches:
82 class BundleViewTest(BundleTestBase):
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)
90 def testNonEmptyBundle(self):
91 self.bundle.append_patch(self.patches[0])
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)
98 def testBundleOrder(self):
99 for patch in self.patches:
100 self.bundle.append_patch(patch)
102 response = self.client.get(bundle_url(self.bundle))
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)
111 # reorder and recheck
113 for patch in self.patches.__reversed__():
114 bundlepatch = BundlePatch.objects.get(bundle = self.bundle,
116 bundlepatch.order = i
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)
128 class BundleUpdateTest(BundleTestBase):
131 super(BundleUpdateTest, self).setUp()
132 self.newname = 'newbundlename'
134 def checkPatchformErrors(self, response):
135 formname = 'patchform'
136 if not formname in response.context:
138 form = response.context[formname]
141 self.assertEquals(form.errors, {})
143 def publicString(self, public):
148 def testNoAction(self):
151 'name': self.newname,
152 'public': self.publicString(not self.bundle.public)
154 response = self.client.post(bundle_url(self.bundle), data)
155 self.assertEqual(response.status_code, 200)
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)
161 def testUpdateName(self):
162 newname = 'newbundlename'
167 'public': self.publicString(self.bundle.public)
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)
175 def testUpdatePublic(self):
176 newname = 'newbundlename'
180 'name': self.bundle.name,
181 'public': self.publicString(not self.bundle.public)
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)
189 # check other forms for errors
190 self.checkPatchformErrors(response)
192 class BundleMaintainerUpdateTest(BundleUpdateTest):
195 super(BundleMaintainerUpdateTest, self).setUp()
196 profile = self.user.get_profile()
197 profile.maintainer_projects.add(defaults.project)
200 class BundlePublicViewTest(BundleTestBase):
203 super(BundlePublicViewTest, self).setUp()
205 self.bundle.append_patch(self.patches[0])
206 self.url = bundle_url(self.bundle)
208 def testPublicBundle(self):
209 self.bundle.public = True
211 response = self.client.get(self.url)
212 self.assertEqual(response.status_code, 200)
213 self.assertContains(response, self.patches[0].name)
215 def testPrivateBundle(self):
216 self.bundle.public = False
218 response = self.client.get(self.url)
219 self.assertEqual(response.status_code, 404)
221 class BundlePublicViewMboxTest(BundlePublicViewTest):
223 super(BundlePublicViewMboxTest, self).setUp()
224 self.url = bundle_url(self.bundle) + "mbox/"
226 class BundlePublicModifyTest(BundleTestBase):
227 """Ensure that non-owners can't modify bundles"""
230 super(BundlePublicModifyTest, self).setUp()
231 self.bundle.public = True
233 self.other_user = create_user()
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"')
242 def testBundleFormSubmission(self):
243 oldname = 'oldbundlename'
244 newname = 'newbundlename'
250 self.bundle.name = oldname
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)
261 self.bundle.name = oldname
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)
271 class BundleCreateFromListTest(BundleTestBase):
272 def testCreateEmptyBundle(self):
273 newbundlename = 'testbundle-new'
274 params = {'form': 'patchlistform',
275 'bundle_name': newbundlename,
277 'project': defaults.project.id}
279 response = self.client.post(
280 '/project/%s/list/' % defaults.project.linkname,
283 self.assertContains(response, 'Bundle %s created' % newbundlename)
285 def testCreateNonEmptyBundle(self):
286 newbundlename = 'testbundle-new'
287 patch = self.patches[0]
289 params = {'form': 'patchlistform',
290 'bundle_name': newbundlename,
292 'project': defaults.project.id,
293 'patch_id:%d' % patch.id: 'checked'}
295 response = self.client.post(
296 '/project/%s/list/' % defaults.project.linkname,
299 self.assertContains(response, 'Bundle %s created' % newbundlename)
300 self.assertContains(response, 'added to bundle %s' % newbundlename,
303 bundle = Bundle.objects.get(name = newbundlename)
304 self.failUnlessEqual(bundle.patches.count(), 1)
305 self.failUnlessEqual(bundle.patches.all()[0], patch)
307 def testCreateNonEmptyBundleEmptyName(self):
308 newbundlename = 'testbundle-new'
309 patch = self.patches[0]
311 n_bundles = Bundle.objects.count()
313 params = {'form': 'patchlistform',
316 'project': defaults.project.id,
317 'patch_id:%d' % patch.id: 'checked'}
319 response = self.client.post(
320 '/project/%s/list/' % defaults.project.linkname,
323 self.assertContains(response, 'No bundle name was specified',
326 # test that no new bundles are present
327 self.failUnlessEqual(n_bundles, Bundle.objects.count())
329 def testCreateDuplicateName(self):
330 newbundlename = 'testbundle-dup'
331 patch = self.patches[0]
333 params = {'form': 'patchlistform',
334 'bundle_name': newbundlename,
336 'project': defaults.project.id,
337 'patch_id:%d' % patch.id: 'checked'}
339 response = self.client.post(
340 '/project/%s/list/' % defaults.project.linkname,
343 n_bundles = Bundle.objects.count()
344 self.assertContains(response, 'Bundle %s created' % newbundlename)
345 self.assertContains(response, 'added to bundle %s' % newbundlename,
348 bundle = Bundle.objects.get(name = newbundlename)
349 self.failUnlessEqual(bundle.patches.count(), 1)
350 self.failUnlessEqual(bundle.patches.all()[0], patch)
352 response = self.client.post(
353 '/project/%s/list/' % defaults.project.linkname,
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)
361 class BundleCreateFromPatchTest(BundleTestBase):
362 def testCreateNonEmptyBundle(self):
363 newbundlename = 'testbundle-new'
364 patch = self.patches[0]
366 params = {'name': newbundlename,
367 'action': 'createbundle'}
369 response = self.client.post('/patch/%d/' % patch.id, params)
371 self.assertContains(response,
372 'Bundle %s created' % newbundlename)
374 bundle = Bundle.objects.get(name = newbundlename)
375 self.failUnlessEqual(bundle.patches.count(), 1)
376 self.failUnlessEqual(bundle.patches.all()[0], patch)
378 def testCreateWithExistingName(self):
379 newbundlename = self.bundle.name
380 patch = self.patches[0]
382 params = {'name': newbundlename,
383 'action': 'createbundle'}
385 response = self.client.post('/patch/%d/' % patch.id, params)
387 self.assertContains(response,
388 'A bundle called %s already exists' % newbundlename)
390 count = Bundle.objects.count()
391 self.failUnlessEqual(Bundle.objects.count(), 1)
393 class BundleAddFromListTest(BundleTestBase):
394 def testAddToEmptyBundle(self):
395 patch = self.patches[0]
396 params = {'form': 'patchlistform',
398 'project': defaults.project.id,
399 'bundle_id': self.bundle.id,
400 'patch_id:%d' % patch.id: 'checked'}
402 response = self.client.post(
403 '/project/%s/list/' % defaults.project.linkname,
406 self.assertContains(response, 'added to bundle %s' % self.bundle.name,
409 self.failUnlessEqual(self.bundle.patches.count(), 1)
410 self.failUnlessEqual(self.bundle.patches.all()[0], patch)
412 def testAddToNonEmptyBundle(self):
413 self.bundle.append_patch(self.patches[0])
414 patch = self.patches[1]
415 params = {'form': 'patchlistform',
417 'project': defaults.project.id,
418 'bundle_id': self.bundle.id,
419 'patch_id:%d' % patch.id: 'checked'}
421 response = self.client.post(
422 '/project/%s/list/' % defaults.project.linkname,
425 self.assertContains(response, 'added to bundle %s' % self.bundle.name,
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())
433 bps = [ BundlePatch.objects.get(bundle = self.bundle,
434 patch = self.patches[i]) \
436 self.failUnless(bps[0].order < bps[1].order)
438 def testAddDuplicate(self):
439 self.bundle.append_patch(self.patches[0])
440 count = self.bundle.patches.count()
441 patch = self.patches[0]
443 params = {'form': 'patchlistform',
445 'project': defaults.project.id,
446 'bundle_id': self.bundle.id,
447 'patch_id:%d' % patch.id: 'checked'}
449 response = self.client.post(
450 '/project/%s/list/' % defaults.project.linkname,
453 self.assertContains(response, 'Patch '%s' already in bundle' \
454 % patch.name, count = 1, status_code = 200)
456 self.assertEquals(count, self.bundle.patches.count())
458 def testAddNewAndDuplicate(self):
459 self.bundle.append_patch(self.patches[0])
460 count = self.bundle.patches.count()
461 patch = self.patches[0]
463 params = {'form': 'patchlistform',
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'}
470 response = self.client.post(
471 '/project/%s/list/' % defaults.project.linkname,
474 self.assertContains(response, 'Patch '%s' already in bundle' \
475 % patch.name, count = 1, status_code = 200)
476 self.assertContains(response, 'Patch '%s' added to bundle' \
477 % self.patches[1].name, count = 1,
479 self.assertEquals(count + 1, self.bundle.patches.count())
481 class BundleAddFromPatchTest(BundleTestBase):
482 def testAddToEmptyBundle(self):
483 patch = self.patches[0]
484 params = {'action': 'addtobundle',
485 'bundle_id': self.bundle.id}
487 response = self.client.post('/patch/%d/' % patch.id, params)
489 self.assertContains(response,
490 'added to bundle "%s"' % self.bundle.name,
493 self.failUnlessEqual(self.bundle.patches.count(), 1)
494 self.failUnlessEqual(self.bundle.patches.all()[0], patch)
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}
502 response = self.client.post('/patch/%d/' % patch.id, params)
504 self.assertContains(response,
505 'added to bundle "%s"' % self.bundle.name,
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())
513 bps = [ BundlePatch.objects.get(bundle = self.bundle,
514 patch = self.patches[i]) \
516 self.failUnless(bps[0].order < bps[1].order)
518 class BundleInitialOrderTest(BundleTestBase):
519 """When creating bundles from a patch list, ensure that the patches in the
520 bundle are ordered by date"""
523 super(BundleInitialOrderTest, self).setUp(5)
525 # put patches in an arbitrary order
526 idxs = [2, 4, 3, 1, 0]
527 self.patches = [ self.patches[i] for i in idxs ]
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)
536 def _testOrder(self, ids, expected_order):
537 newbundlename = 'testbundle-new'
539 # need to define our querystring explicity to enforce ordering
540 params = {'form': 'patchlistform',
541 'bundle_name': newbundlename,
543 'project': defaults.project.id,
546 data = urlencode(params) + \
547 ''.join([ '&patch_id:%d=checked' % i for i in ids ])
549 response = self.client.post(
550 '/project/%s/list/' % defaults.project.linkname,
552 content_type = 'application/x-www-form-urlencoded',
555 self.assertContains(response, 'Bundle %s created' % newbundlename)
556 self.assertContains(response, 'added to bundle %s' % newbundlename,
559 bundle = Bundle.objects.get(name = newbundlename)
561 # BundlePatches should be sorted by .order by default
562 bps = BundlePatch.objects.filter(bundle = bundle)
564 for (bp, p) in zip(bps, expected_order):
565 self.assertEqual(bp.patch.pk, p.pk)
569 def testBundleForwardOrder(self):
570 ids = map(lambda p: p.id, self.patches)
571 self._testOrder(ids, self.patches)
573 def testBundleReverseOrder(self):
574 ids = map(lambda p: p.id, self.patches)
576 self._testOrder(ids, self.patches)
578 class BundleReorderTest(BundleTestBase):
580 super(BundleReorderTest, self).setUp(5)
582 self.bundle.append_patch(self.patches[i])
584 def checkReordering(self, neworder, start, end):
585 neworder_ids = [ self.patches[i].id for i in neworder ]
587 firstpatch = BundlePatch.objects.get(bundle = self.bundle,
588 patch = self.patches[start]).patch
590 slice_ids = neworder_ids[start:end]
591 params = {'form': 'reorderform',
592 'order_start': firstpatch.id,
593 'neworder': slice_ids}
595 response = self.client.post(bundle_url(self.bundle), params)
597 self.failUnlessEqual(response.status_code, 200)
599 bps = BundlePatch.objects.filter(bundle = self.bundle) \
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)
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)
611 def testBundleReorderAll(self):
612 # reorder all patches:
613 self.checkReordering([2,1,4,0,3], 0, 5)
615 def testBundleReorderEnd(self):
616 # reorder only the last three patches
617 self.checkReordering([0,1,3,2,4], 2, 5)
619 def testBundleReorderBegin(self):
620 # reorder only the first three patches
621 self.checkReordering([2,0,1,3,4], 0, 3)
623 def testBundleReorderMiddle(self):
624 # reorder only 2nd, 3rd, and 4th patches
625 self.checkReordering([0,2,3,1,4], 1, 4)
627 class BundleRedirTest(BundleTestBase):
628 # old URL: private bundles used to be under /user/bundle/<id>
631 super(BundleRedirTest, self).setUp()
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))
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,