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 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,
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),
75 self.patches.append(patch)
78 for patch in self.patches:
83 class BundleViewTest(BundleTestBase):
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)
91 def testNonEmptyBundle(self):
92 self.bundle.append_patch(self.patches[0])
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)
99 def testBundleOrder(self):
100 for patch in self.patches:
101 self.bundle.append_patch(patch)
103 response = self.client.get(bundle_url(self.bundle))
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)
112 # reorder and recheck
114 for patch in self.patches.__reversed__():
115 bundlepatch = BundlePatch.objects.get(bundle = self.bundle,
117 bundlepatch.order = i
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)
129 class BundleUpdateTest(BundleTestBase):
132 super(BundleUpdateTest, self).setUp()
133 self.newname = 'newbundlename'
135 def checkPatchformErrors(self, response):
136 formname = 'patchform'
137 if not formname in response.context:
139 form = response.context[formname]
142 self.assertEquals(form.errors, {})
144 def publicString(self, public):
149 def testNoAction(self):
152 'name': self.newname,
153 'public': self.publicString(not self.bundle.public)
155 response = self.client.post(bundle_url(self.bundle), data)
156 self.assertEqual(response.status_code, 200)
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)
162 def testUpdateName(self):
163 newname = 'newbundlename'
168 'public': self.publicString(self.bundle.public)
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)
176 def testUpdatePublic(self):
177 newname = 'newbundlename'
181 'name': self.bundle.name,
182 'public': self.publicString(not self.bundle.public)
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)
190 # check other forms for errors
191 self.checkPatchformErrors(response)
193 class BundleMaintainerUpdateTest(BundleUpdateTest):
196 super(BundleMaintainerUpdateTest, self).setUp()
197 profile = self.user.profile
198 profile.maintainer_projects.add(defaults.project)
201 class BundlePublicViewTest(BundleTestBase):
204 super(BundlePublicViewTest, self).setUp()
206 self.bundle.append_patch(self.patches[0])
207 self.url = bundle_url(self.bundle)
209 def testPublicBundle(self):
210 self.bundle.public = True
212 response = self.client.get(self.url)
213 self.assertEqual(response.status_code, 200)
214 self.assertContains(response, self.patches[0].name)
216 def testPrivateBundle(self):
217 self.bundle.public = False
219 response = self.client.get(self.url)
220 self.assertEqual(response.status_code, 404)
222 class BundlePublicViewMboxTest(BundlePublicViewTest):
224 super(BundlePublicViewMboxTest, self).setUp()
225 self.url = bundle_url(self.bundle) + "mbox/"
227 class BundlePublicModifyTest(BundleTestBase):
228 """Ensure that non-owners can't modify bundles"""
231 super(BundlePublicModifyTest, self).setUp()
232 self.bundle.public = True
234 self.other_user = create_user()
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')
244 def testBundleFormSubmission(self):
245 oldname = 'oldbundlename'
246 newname = 'newbundlename'
252 self.bundle.name = oldname
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)
263 self.bundle.name = oldname
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)
273 class BundleCreateFromListTest(BundleTestBase):
274 def testCreateEmptyBundle(self):
275 newbundlename = 'testbundle-new'
276 params = {'form': 'patchlistform',
277 'bundle_name': newbundlename,
279 'project': defaults.project.id}
281 response = self.client.post(
282 '/project/%s/list/' % defaults.project.linkname,
285 self.assertContains(response, 'Bundle %s created' % newbundlename)
287 def testCreateNonEmptyBundle(self):
288 newbundlename = 'testbundle-new'
289 patch = self.patches[0]
291 params = {'form': 'patchlistform',
292 'bundle_name': newbundlename,
294 'project': defaults.project.id,
295 'patch_id:%d' % patch.id: 'checked'}
297 response = self.client.post(
298 '/project/%s/list/' % defaults.project.linkname,
301 self.assertContains(response, 'Bundle %s created' % newbundlename)
302 self.assertContains(response, 'added to bundle %s' % newbundlename,
305 bundle = Bundle.objects.get(name = newbundlename)
306 self.failUnlessEqual(bundle.patches.count(), 1)
307 self.failUnlessEqual(bundle.patches.all()[0], patch)
309 def testCreateNonEmptyBundleEmptyName(self):
310 newbundlename = 'testbundle-new'
311 patch = self.patches[0]
313 n_bundles = Bundle.objects.count()
315 params = {'form': 'patchlistform',
318 'project': defaults.project.id,
319 'patch_id:%d' % patch.id: 'checked'}
321 response = self.client.post(
322 '/project/%s/list/' % defaults.project.linkname,
325 self.assertContains(response, 'No bundle name was specified',
328 # test that no new bundles are present
329 self.failUnlessEqual(n_bundles, Bundle.objects.count())
331 def testCreateDuplicateName(self):
332 newbundlename = 'testbundle-dup'
333 patch = self.patches[0]
335 params = {'form': 'patchlistform',
336 'bundle_name': newbundlename,
338 'project': defaults.project.id,
339 'patch_id:%d' % patch.id: 'checked'}
341 response = self.client.post(
342 '/project/%s/list/' % defaults.project.linkname,
345 n_bundles = Bundle.objects.count()
346 self.assertContains(response, 'Bundle %s created' % newbundlename)
347 self.assertContains(response, 'added to bundle %s' % newbundlename,
350 bundle = Bundle.objects.get(name = newbundlename)
351 self.failUnlessEqual(bundle.patches.count(), 1)
352 self.failUnlessEqual(bundle.patches.all()[0], patch)
354 response = self.client.post(
355 '/project/%s/list/' % defaults.project.linkname,
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)
363 class BundleCreateFromPatchTest(BundleTestBase):
364 def testCreateNonEmptyBundle(self):
365 newbundlename = 'testbundle-new'
366 patch = self.patches[0]
368 params = {'name': newbundlename,
369 'action': 'createbundle'}
371 response = self.client.post('/patch/%d/' % patch.id, params)
373 self.assertContains(response,
374 'Bundle %s created' % newbundlename)
376 bundle = Bundle.objects.get(name = newbundlename)
377 self.failUnlessEqual(bundle.patches.count(), 1)
378 self.failUnlessEqual(bundle.patches.all()[0], patch)
380 def testCreateWithExistingName(self):
381 newbundlename = self.bundle.name
382 patch = self.patches[0]
384 params = {'name': newbundlename,
385 'action': 'createbundle'}
387 response = self.client.post('/patch/%d/' % patch.id, params)
389 self.assertContains(response,
390 'A bundle called %s already exists' % newbundlename)
392 count = Bundle.objects.count()
393 self.failUnlessEqual(Bundle.objects.count(), 1)
395 class BundleAddFromListTest(BundleTestBase):
396 def testAddToEmptyBundle(self):
397 patch = self.patches[0]
398 params = {'form': 'patchlistform',
400 'project': defaults.project.id,
401 'bundle_id': self.bundle.id,
402 'patch_id:%d' % patch.id: 'checked'}
404 response = self.client.post(
405 '/project/%s/list/' % defaults.project.linkname,
408 self.assertContains(response, 'added to bundle %s' % self.bundle.name,
411 self.failUnlessEqual(self.bundle.patches.count(), 1)
412 self.failUnlessEqual(self.bundle.patches.all()[0], patch)
414 def testAddToNonEmptyBundle(self):
415 self.bundle.append_patch(self.patches[0])
416 patch = self.patches[1]
417 params = {'form': 'patchlistform',
419 'project': defaults.project.id,
420 'bundle_id': self.bundle.id,
421 'patch_id:%d' % patch.id: 'checked'}
423 response = self.client.post(
424 '/project/%s/list/' % defaults.project.linkname,
427 self.assertContains(response, 'added to bundle %s' % self.bundle.name,
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())
435 bps = [ BundlePatch.objects.get(bundle = self.bundle,
436 patch = self.patches[i]) \
438 self.failUnless(bps[0].order < bps[1].order)
440 def testAddDuplicate(self):
441 self.bundle.append_patch(self.patches[0])
442 count = self.bundle.patches.count()
443 patch = self.patches[0]
445 params = {'form': 'patchlistform',
447 'project': defaults.project.id,
448 'bundle_id': self.bundle.id,
449 'patch_id:%d' % patch.id: 'checked'}
451 response = self.client.post(
452 '/project/%s/list/' % defaults.project.linkname,
455 self.assertContains(response, 'Patch '%s' already in bundle' \
456 % patch.name, count = 1, status_code = 200)
458 self.assertEquals(count, self.bundle.patches.count())
460 def testAddNewAndDuplicate(self):
461 self.bundle.append_patch(self.patches[0])
462 count = self.bundle.patches.count()
463 patch = self.patches[0]
465 params = {'form': 'patchlistform',
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'}
472 response = self.client.post(
473 '/project/%s/list/' % defaults.project.linkname,
476 self.assertContains(response, 'Patch '%s' already in bundle' \
477 % patch.name, count = 1, status_code = 200)
478 self.assertContains(response, 'Patch '%s' added to bundle' \
479 % self.patches[1].name, count = 1,
481 self.assertEquals(count + 1, self.bundle.patches.count())
483 class BundleAddFromPatchTest(BundleTestBase):
484 def testAddToEmptyBundle(self):
485 patch = self.patches[0]
486 params = {'action': 'addtobundle',
487 'bundle_id': self.bundle.id}
489 response = self.client.post('/patch/%d/' % patch.id, params)
491 self.assertContains(response,
492 'added to bundle "%s"' % self.bundle.name,
495 self.failUnlessEqual(self.bundle.patches.count(), 1)
496 self.failUnlessEqual(self.bundle.patches.all()[0], patch)
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}
504 response = self.client.post('/patch/%d/' % patch.id, params)
506 self.assertContains(response,
507 'added to bundle "%s"' % self.bundle.name,
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())
515 bps = [ BundlePatch.objects.get(bundle = self.bundle,
516 patch = self.patches[i]) \
518 self.failUnless(bps[0].order < bps[1].order)
520 class BundleInitialOrderTest(BundleTestBase):
521 """When creating bundles from a patch list, ensure that the patches in the
522 bundle are ordered by date"""
525 super(BundleInitialOrderTest, self).setUp(5)
527 # put patches in an arbitrary order
528 idxs = [2, 4, 3, 1, 0]
529 self.patches = [ self.patches[i] for i in idxs ]
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)
538 def _testOrder(self, ids, expected_order):
539 newbundlename = 'testbundle-new'
541 # need to define our querystring explicity to enforce ordering
542 params = {'form': 'patchlistform',
543 'bundle_name': newbundlename,
545 'project': defaults.project.id,
548 data = urlencode(params) + \
549 ''.join([ '&patch_id:%d=checked' % i for i in ids ])
551 response = self.client.post(
552 '/project/%s/list/' % defaults.project.linkname,
554 content_type = 'application/x-www-form-urlencoded',
557 self.assertContains(response, 'Bundle %s created' % newbundlename)
558 self.assertContains(response, 'added to bundle %s' % newbundlename,
561 bundle = Bundle.objects.get(name = newbundlename)
563 # BundlePatches should be sorted by .order by default
564 bps = BundlePatch.objects.filter(bundle = bundle)
566 for (bp, p) in zip(bps, expected_order):
567 self.assertEqual(bp.patch.pk, p.pk)
571 def testBundleForwardOrder(self):
572 ids = map(lambda p: p.id, self.patches)
573 self._testOrder(ids, self.patches)
575 def testBundleReverseOrder(self):
576 ids = map(lambda p: p.id, self.patches)
578 self._testOrder(ids, self.patches)
580 class BundleReorderTest(BundleTestBase):
582 super(BundleReorderTest, self).setUp(5)
584 self.bundle.append_patch(self.patches[i])
586 def checkReordering(self, neworder, start, end):
587 neworder_ids = [ self.patches[i].id for i in neworder ]
589 firstpatch = BundlePatch.objects.get(bundle = self.bundle,
590 patch = self.patches[start]).patch
592 slice_ids = neworder_ids[start:end]
593 params = {'form': 'reorderform',
594 'order_start': firstpatch.id,
595 'neworder': slice_ids}
597 response = self.client.post(bundle_url(self.bundle), params)
599 self.failUnlessEqual(response.status_code, 200)
601 bps = BundlePatch.objects.filter(bundle = self.bundle) \
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)
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)
613 def testBundleReorderAll(self):
614 # reorder all patches:
615 self.checkReordering([2,1,4,0,3], 0, 5)
617 def testBundleReorderEnd(self):
618 # reorder only the last three patches
619 self.checkReordering([0,1,3,2,4], 2, 5)
621 def testBundleReorderBegin(self):
622 # reorder only the first three patches
623 self.checkReordering([2,0,1,3,4], 0, 3)
625 def testBundleReorderMiddle(self):
626 # reorder only 2nd, 3rd, and 4th patches
627 self.checkReordering([0,2,3,1,4], 1, 4)
629 class BundleRedirTest(BundleTestBase):
630 # old URL: private bundles used to be under /user/bundle/<id>
633 super(BundleRedirTest, self).setUp()
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))
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,