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"')
241 self.assertNotContains(response, 'Change order')
243 def testBundleFormSubmission(self):
244 oldname = 'oldbundlename'
245 newname = 'newbundlename'
251 self.bundle.name = oldname
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)
262 self.bundle.name = oldname
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)
272 class BundleCreateFromListTest(BundleTestBase):
273 def testCreateEmptyBundle(self):
274 newbundlename = 'testbundle-new'
275 params = {'form': 'patchlistform',
276 'bundle_name': newbundlename,
278 'project': defaults.project.id}
280 response = self.client.post(
281 '/project/%s/list/' % defaults.project.linkname,
284 self.assertContains(response, 'Bundle %s created' % newbundlename)
286 def testCreateNonEmptyBundle(self):
287 newbundlename = 'testbundle-new'
288 patch = self.patches[0]
290 params = {'form': 'patchlistform',
291 'bundle_name': newbundlename,
293 'project': defaults.project.id,
294 'patch_id:%d' % patch.id: 'checked'}
296 response = self.client.post(
297 '/project/%s/list/' % defaults.project.linkname,
300 self.assertContains(response, 'Bundle %s created' % newbundlename)
301 self.assertContains(response, 'added to bundle %s' % newbundlename,
304 bundle = Bundle.objects.get(name = newbundlename)
305 self.failUnlessEqual(bundle.patches.count(), 1)
306 self.failUnlessEqual(bundle.patches.all()[0], patch)
308 def testCreateNonEmptyBundleEmptyName(self):
309 newbundlename = 'testbundle-new'
310 patch = self.patches[0]
312 n_bundles = Bundle.objects.count()
314 params = {'form': 'patchlistform',
317 'project': defaults.project.id,
318 'patch_id:%d' % patch.id: 'checked'}
320 response = self.client.post(
321 '/project/%s/list/' % defaults.project.linkname,
324 self.assertContains(response, 'No bundle name was specified',
327 # test that no new bundles are present
328 self.failUnlessEqual(n_bundles, Bundle.objects.count())
330 def testCreateDuplicateName(self):
331 newbundlename = 'testbundle-dup'
332 patch = self.patches[0]
334 params = {'form': 'patchlistform',
335 'bundle_name': newbundlename,
337 'project': defaults.project.id,
338 'patch_id:%d' % patch.id: 'checked'}
340 response = self.client.post(
341 '/project/%s/list/' % defaults.project.linkname,
344 n_bundles = Bundle.objects.count()
345 self.assertContains(response, 'Bundle %s created' % newbundlename)
346 self.assertContains(response, 'added to bundle %s' % newbundlename,
349 bundle = Bundle.objects.get(name = newbundlename)
350 self.failUnlessEqual(bundle.patches.count(), 1)
351 self.failUnlessEqual(bundle.patches.all()[0], patch)
353 response = self.client.post(
354 '/project/%s/list/' % defaults.project.linkname,
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)
362 class BundleCreateFromPatchTest(BundleTestBase):
363 def testCreateNonEmptyBundle(self):
364 newbundlename = 'testbundle-new'
365 patch = self.patches[0]
367 params = {'name': newbundlename,
368 'action': 'createbundle'}
370 response = self.client.post('/patch/%d/' % patch.id, params)
372 self.assertContains(response,
373 'Bundle %s created' % newbundlename)
375 bundle = Bundle.objects.get(name = newbundlename)
376 self.failUnlessEqual(bundle.patches.count(), 1)
377 self.failUnlessEqual(bundle.patches.all()[0], patch)
379 def testCreateWithExistingName(self):
380 newbundlename = self.bundle.name
381 patch = self.patches[0]
383 params = {'name': newbundlename,
384 'action': 'createbundle'}
386 response = self.client.post('/patch/%d/' % patch.id, params)
388 self.assertContains(response,
389 'A bundle called %s already exists' % newbundlename)
391 count = Bundle.objects.count()
392 self.failUnlessEqual(Bundle.objects.count(), 1)
394 class BundleAddFromListTest(BundleTestBase):
395 def testAddToEmptyBundle(self):
396 patch = self.patches[0]
397 params = {'form': 'patchlistform',
399 'project': defaults.project.id,
400 'bundle_id': self.bundle.id,
401 'patch_id:%d' % patch.id: 'checked'}
403 response = self.client.post(
404 '/project/%s/list/' % defaults.project.linkname,
407 self.assertContains(response, 'added to bundle %s' % self.bundle.name,
410 self.failUnlessEqual(self.bundle.patches.count(), 1)
411 self.failUnlessEqual(self.bundle.patches.all()[0], patch)
413 def testAddToNonEmptyBundle(self):
414 self.bundle.append_patch(self.patches[0])
415 patch = self.patches[1]
416 params = {'form': 'patchlistform',
418 'project': defaults.project.id,
419 'bundle_id': self.bundle.id,
420 'patch_id:%d' % patch.id: 'checked'}
422 response = self.client.post(
423 '/project/%s/list/' % defaults.project.linkname,
426 self.assertContains(response, 'added to bundle %s' % self.bundle.name,
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())
434 bps = [ BundlePatch.objects.get(bundle = self.bundle,
435 patch = self.patches[i]) \
437 self.failUnless(bps[0].order < bps[1].order)
439 def testAddDuplicate(self):
440 self.bundle.append_patch(self.patches[0])
441 count = self.bundle.patches.count()
442 patch = self.patches[0]
444 params = {'form': 'patchlistform',
446 'project': defaults.project.id,
447 'bundle_id': self.bundle.id,
448 'patch_id:%d' % patch.id: 'checked'}
450 response = self.client.post(
451 '/project/%s/list/' % defaults.project.linkname,
454 self.assertContains(response, 'Patch '%s' already in bundle' \
455 % patch.name, count = 1, status_code = 200)
457 self.assertEquals(count, self.bundle.patches.count())
459 def testAddNewAndDuplicate(self):
460 self.bundle.append_patch(self.patches[0])
461 count = self.bundle.patches.count()
462 patch = self.patches[0]
464 params = {'form': 'patchlistform',
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'}
471 response = self.client.post(
472 '/project/%s/list/' % defaults.project.linkname,
475 self.assertContains(response, 'Patch '%s' already in bundle' \
476 % patch.name, count = 1, status_code = 200)
477 self.assertContains(response, 'Patch '%s' added to bundle' \
478 % self.patches[1].name, count = 1,
480 self.assertEquals(count + 1, self.bundle.patches.count())
482 class BundleAddFromPatchTest(BundleTestBase):
483 def testAddToEmptyBundle(self):
484 patch = self.patches[0]
485 params = {'action': 'addtobundle',
486 'bundle_id': self.bundle.id}
488 response = self.client.post('/patch/%d/' % patch.id, params)
490 self.assertContains(response,
491 'added to bundle "%s"' % self.bundle.name,
494 self.failUnlessEqual(self.bundle.patches.count(), 1)
495 self.failUnlessEqual(self.bundle.patches.all()[0], patch)
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}
503 response = self.client.post('/patch/%d/' % patch.id, params)
505 self.assertContains(response,
506 'added to bundle "%s"' % self.bundle.name,
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())
514 bps = [ BundlePatch.objects.get(bundle = self.bundle,
515 patch = self.patches[i]) \
517 self.failUnless(bps[0].order < bps[1].order)
519 class BundleInitialOrderTest(BundleTestBase):
520 """When creating bundles from a patch list, ensure that the patches in the
521 bundle are ordered by date"""
524 super(BundleInitialOrderTest, self).setUp(5)
526 # put patches in an arbitrary order
527 idxs = [2, 4, 3, 1, 0]
528 self.patches = [ self.patches[i] for i in idxs ]
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)
537 def _testOrder(self, ids, expected_order):
538 newbundlename = 'testbundle-new'
540 # need to define our querystring explicity to enforce ordering
541 params = {'form': 'patchlistform',
542 'bundle_name': newbundlename,
544 'project': defaults.project.id,
547 data = urlencode(params) + \
548 ''.join([ '&patch_id:%d=checked' % i for i in ids ])
550 response = self.client.post(
551 '/project/%s/list/' % defaults.project.linkname,
553 content_type = 'application/x-www-form-urlencoded',
556 self.assertContains(response, 'Bundle %s created' % newbundlename)
557 self.assertContains(response, 'added to bundle %s' % newbundlename,
560 bundle = Bundle.objects.get(name = newbundlename)
562 # BundlePatches should be sorted by .order by default
563 bps = BundlePatch.objects.filter(bundle = bundle)
565 for (bp, p) in zip(bps, expected_order):
566 self.assertEqual(bp.patch.pk, p.pk)
570 def testBundleForwardOrder(self):
571 ids = map(lambda p: p.id, self.patches)
572 self._testOrder(ids, self.patches)
574 def testBundleReverseOrder(self):
575 ids = map(lambda p: p.id, self.patches)
577 self._testOrder(ids, self.patches)
579 class BundleReorderTest(BundleTestBase):
581 super(BundleReorderTest, self).setUp(5)
583 self.bundle.append_patch(self.patches[i])
585 def checkReordering(self, neworder, start, end):
586 neworder_ids = [ self.patches[i].id for i in neworder ]
588 firstpatch = BundlePatch.objects.get(bundle = self.bundle,
589 patch = self.patches[start]).patch
591 slice_ids = neworder_ids[start:end]
592 params = {'form': 'reorderform',
593 'order_start': firstpatch.id,
594 'neworder': slice_ids}
596 response = self.client.post(bundle_url(self.bundle), params)
598 self.failUnlessEqual(response.status_code, 200)
600 bps = BundlePatch.objects.filter(bundle = self.bundle) \
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)
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)
612 def testBundleReorderAll(self):
613 # reorder all patches:
614 self.checkReordering([2,1,4,0,3], 0, 5)
616 def testBundleReorderEnd(self):
617 # reorder only the last three patches
618 self.checkReordering([0,1,3,2,4], 2, 5)
620 def testBundleReorderBegin(self):
621 # reorder only the first three patches
622 self.checkReordering([2,0,1,3,4], 0, 3)
624 def testBundleReorderMiddle(self):
625 # reorder only 2nd, 3rd, and 4th patches
626 self.checkReordering([0,2,3,1,4], 1, 4)
628 class BundleRedirTest(BundleTestBase):
629 # old URL: private bundles used to be under /user/bundle/<id>
632 super(BundleRedirTest, self).setUp()
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))
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,