]> git.ozlabs.org Git - patchwork/commitdiff
tests: Make tests compatible with django 1.6
authorJeremy Kerr <jk@ozlabs.org>
Wed, 23 Apr 2014 13:02:46 +0000 (21:02 +0800)
committerJeremy Kerr <jk@ozlabs.org>
Wed, 23 Apr 2014 13:05:19 +0000 (21:05 +0800)
The default test runner in django 1.6 relies on tests being named
test*.py, rather that an explicit <appname>.test module.

Signed-off-by: Jeremy Kerr <jk@ozlabs.org>
29 files changed:
apps/patchwork/tests/__init__.py
apps/patchwork/tests/bundles.py [deleted file]
apps/patchwork/tests/confirm.py [deleted file]
apps/patchwork/tests/encodings.py [deleted file]
apps/patchwork/tests/expiry.py [deleted file]
apps/patchwork/tests/filters.py [deleted file]
apps/patchwork/tests/list.py [deleted file]
apps/patchwork/tests/mail_settings.py [deleted file]
apps/patchwork/tests/mboxviews.py [deleted file]
apps/patchwork/tests/notifications.py [deleted file]
apps/patchwork/tests/patchparser.py [deleted file]
apps/patchwork/tests/person.py [deleted file]
apps/patchwork/tests/registration.py [deleted file]
apps/patchwork/tests/test_bundles.py [new file with mode: 0644]
apps/patchwork/tests/test_confirm.py [new file with mode: 0644]
apps/patchwork/tests/test_encodings.py [new file with mode: 0644]
apps/patchwork/tests/test_expiry.py [new file with mode: 0644]
apps/patchwork/tests/test_filters.py [new file with mode: 0644]
apps/patchwork/tests/test_list.py [new file with mode: 0644]
apps/patchwork/tests/test_mail_settings.py [new file with mode: 0644]
apps/patchwork/tests/test_mboxviews.py [new file with mode: 0644]
apps/patchwork/tests/test_notifications.py [new file with mode: 0644]
apps/patchwork/tests/test_patchparser.py [new file with mode: 0644]
apps/patchwork/tests/test_person.py [new file with mode: 0644]
apps/patchwork/tests/test_registration.py [new file with mode: 0644]
apps/patchwork/tests/test_updates.py [new file with mode: 0644]
apps/patchwork/tests/test_user.py [new file with mode: 0644]
apps/patchwork/tests/updates.py [deleted file]
apps/patchwork/tests/user.py [deleted file]

index 5dfcdee27c19343c15612c7711581cf635f760b2..792d7dbc2a30277c67d77b3157799dc25dd522b4 100644 (file)
 # along with Patchwork; if not, write to the Free Software
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
-from patchwork.tests.patchparser import *
-from patchwork.tests.encodings import *
-from patchwork.tests.bundles import *
-from patchwork.tests.mboxviews import *
-from patchwork.tests.updates import *
-from patchwork.tests.filters import *
-from patchwork.tests.confirm import *
-from patchwork.tests.registration import *
-from patchwork.tests.user import *
-from patchwork.tests.mail_settings import *
-from patchwork.tests.notifications import *
-from patchwork.tests.list import *
-from patchwork.tests.person import *
-from patchwork.tests.expiry import *
+from patchwork.tests.test_patchparser import *
+from patchwork.tests.test_encodings import *
+from patchwork.tests.test_bundles import *
+from patchwork.tests.test_mboxviews import *
+from patchwork.tests.test_updates import *
+from patchwork.tests.test_filters import *
+from patchwork.tests.test_confirm import *
+from patchwork.tests.test_registration import *
+from patchwork.tests.test_user import *
+from patchwork.tests.test_mail_settings import *
+from patchwork.tests.test_notifications import *
+from patchwork.tests.test_list import *
+from patchwork.tests.test_person import *
+from patchwork.tests.test_expiry import *
diff --git a/apps/patchwork/tests/bundles.py b/apps/patchwork/tests/bundles.py
deleted file mode 100644 (file)
index 5e8b95b..0000000
+++ /dev/null
@@ -1,646 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2009 Jeremy Kerr <jk@ozlabs.org>
-#
-# This file is part of the Patchwork package.
-#
-# Patchwork is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# Patchwork is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with Patchwork; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-import unittest
-import datetime
-from django.test import TestCase
-from django.test.client import Client
-from django.utils.http import urlencode
-from django.conf import settings
-from patchwork.models import Patch, Bundle, BundlePatch, Person
-from patchwork.tests.utils import defaults, create_user, find_in_context
-
-def bundle_url(bundle):
-    return '/bundle/%s/%s/' % (bundle.owner.username, bundle.name)
-
-class BundleListTest(TestCase):
-    def setUp(self):
-        self.user = create_user()
-        self.client.login(username = self.user.username,
-                password = self.user.username)
-
-    def testNoBundles(self):
-        response = self.client.get('/user/bundles/')
-        self.failUnlessEqual(response.status_code, 200)
-        self.failUnlessEqual(
-                len(find_in_context(response.context, 'bundles')), 0)
-
-    def testSingleBundle(self):
-        defaults.project.save()
-        bundle = Bundle(owner = self.user, project = defaults.project)
-        bundle.save()
-        response = self.client.get('/user/bundles/')
-        self.failUnlessEqual(response.status_code, 200)
-        self.failUnlessEqual(
-                len(find_in_context(response.context, 'bundles')), 1)
-
-    def tearDown(self):
-        self.user.delete()
-
-class BundleTestBase(TestCase):
-    def setUp(self, patch_count=3):
-        patch_names = ['testpatch%d' % (i) for i in range(1, patch_count+1)]
-        self.user = create_user()
-        self.client.login(username = self.user.username,
-                password = self.user.username)
-        defaults.project.save()
-        self.bundle = Bundle(owner = self.user, project = defaults.project,
-                name = 'testbundle')
-        self.bundle.save()
-        self.patches = []
-
-        for patch_name in patch_names:
-            patch = Patch(project = defaults.project,
-                               msgid = patch_name, name = patch_name,
-                               submitter = Person.objects.get(user = self.user),
-                               content = '')
-            patch.save()
-            self.patches.append(patch)
-
-    def tearDown(self):
-        for patch in self.patches:
-            patch.delete()
-        self.bundle.delete()
-        self.user.delete()
-
-class BundleViewTest(BundleTestBase):
-
-    def testEmptyBundle(self):
-        response = self.client.get(bundle_url(self.bundle))
-        self.failUnlessEqual(response.status_code, 200)
-        page = find_in_context(response.context, 'page')
-        self.failUnlessEqual(len(page.object_list), 0)
-
-    def testNonEmptyBundle(self):
-        self.bundle.append_patch(self.patches[0])
-
-        response = self.client.get(bundle_url(self.bundle))
-        self.failUnlessEqual(response.status_code, 200)
-        page = find_in_context(response.context, 'page')
-        self.failUnlessEqual(len(page.object_list), 1)
-
-    def testBundleOrder(self):
-        for patch in self.patches:
-            self.bundle.append_patch(patch)
-
-        response = self.client.get(bundle_url(self.bundle))
-
-        pos = 0
-        for patch in self.patches:
-            next_pos = response.content.find(patch.name)
-            # ensure that this patch is after the previous
-            self.failUnless(next_pos > pos)
-            pos = next_pos
-
-        # reorder and recheck
-        i = 0
-        for patch in self.patches.__reversed__():
-            bundlepatch = BundlePatch.objects.get(bundle = self.bundle,
-                    patch = patch)
-            bundlepatch.order = i
-            bundlepatch.save()
-            i += 1
-
-        response = self.client.get(bundle_url(self.bundle))
-        pos = len(response.content)
-        for patch in self.patches:
-            next_pos = response.content.find(patch.name)
-            # ensure that this patch is now *before* the previous
-            self.failUnless(next_pos < pos)
-            pos = next_pos
-
-class BundleUpdateTest(BundleTestBase):
-
-    def setUp(self):
-        super(BundleUpdateTest, self).setUp()
-        self.newname = 'newbundlename'
-
-    def checkPatchformErrors(self, response):
-        formname = 'patchform'
-        if not formname in response.context:
-            return
-        form = response.context[formname]
-        if not form:
-            return
-        self.assertEquals(form.errors, {})
-
-    def publicString(self, public):
-        if public:
-            return 'on'
-        return ''
-
-    def testNoAction(self):
-        data = {
-            'form': 'bundle',
-            'name': self.newname,
-            'public': self.publicString(not self.bundle.public)
-        }
-        response = self.client.post(bundle_url(self.bundle), data)
-        self.assertEqual(response.status_code, 200)
-
-        bundle = Bundle.objects.get(pk = self.bundle.pk)
-        self.assertEqual(bundle.name, self.bundle.name)
-        self.assertEqual(bundle.public, self.bundle.public)
-
-    def testUpdateName(self):
-        newname = 'newbundlename'
-        data = {
-            'form': 'bundle',
-            'action': 'update',
-            'name': newname,
-            'public': self.publicString(self.bundle.public)
-        }
-        response = self.client.post(bundle_url(self.bundle), data)
-        bundle = Bundle.objects.get(pk = self.bundle.pk)
-        self.assertRedirects(response, bundle_url(bundle))
-        self.assertEqual(bundle.name, newname)
-        self.assertEqual(bundle.public, self.bundle.public)
-
-    def testUpdatePublic(self):
-        newname = 'newbundlename'
-        data = {
-            'form': 'bundle',
-            'action': 'update',
-            'name': self.bundle.name,
-            'public': self.publicString(not self.bundle.public)
-        }
-        response = self.client.post(bundle_url(self.bundle), data)
-        self.assertEqual(response.status_code, 200)
-        bundle = Bundle.objects.get(pk = self.bundle.pk)
-        self.assertEqual(bundle.name, self.bundle.name)
-        self.assertEqual(bundle.public, not self.bundle.public)
-
-        # check other forms for errors
-        self.checkPatchformErrors(response)
-
-class BundleMaintainerUpdateTest(BundleUpdateTest):
-
-    def setUp(self):
-        super(BundleMaintainerUpdateTest, self).setUp()
-        profile = self.user.get_profile()
-        profile.maintainer_projects.add(defaults.project)
-        profile.save()
-
-class BundlePublicViewTest(BundleTestBase):
-
-    def setUp(self):
-        super(BundlePublicViewTest, self).setUp()
-        self.client.logout()
-        self.bundle.append_patch(self.patches[0])
-        self.url = bundle_url(self.bundle)
-
-    def testPublicBundle(self):
-        self.bundle.public = True
-        self.bundle.save()
-        response = self.client.get(self.url)
-        self.assertEqual(response.status_code, 200)
-        self.assertContains(response, self.patches[0].name)
-
-    def testPrivateBundle(self):
-        self.bundle.public = False
-        self.bundle.save()
-        response = self.client.get(self.url)
-        self.assertEqual(response.status_code, 404)
-
-class BundlePublicViewMboxTest(BundlePublicViewTest):
-    def setUp(self):
-        super(BundlePublicViewMboxTest, self).setUp()
-        self.url = bundle_url(self.bundle) + "mbox/"
-
-class BundlePublicModifyTest(BundleTestBase):
-    """Ensure that non-owners can't modify bundles"""
-
-    def setUp(self):
-        super(BundlePublicModifyTest, self).setUp()
-        self.bundle.public = True
-        self.bundle.save()
-        self.other_user = create_user()
-
-    def testBundleFormPresence(self):
-        """Check for presence of the modify form on the bundle"""
-        self.client.login(username = self.other_user.username,
-                password = self.other_user.username)
-        response = self.client.get(bundle_url(self.bundle))
-        self.assertNotContains(response, 'name="form" value="bundle"')
-        self.assertNotContains(response, 'Change order')
-
-    def testBundleFormSubmission(self):
-        oldname = 'oldbundlename'
-        newname = 'newbundlename'
-        data = {
-            'form': 'bundle',
-            'action': 'update',
-            'name': newname,
-        }
-        self.bundle.name = oldname
-        self.bundle.save()
-
-        # first, check that we can modify with the owner
-        self.client.login(username = self.user.username,
-                password = self.user.username)
-        response = self.client.post(bundle_url(self.bundle), data)
-        self.bundle = Bundle.objects.get(pk = self.bundle.pk)
-        self.assertEqual(self.bundle.name, newname)
-
-        # reset bundle name
-        self.bundle.name = oldname
-        self.bundle.save()
-
-        # log in with a different user, and check that we can no longer modify
-        self.client.login(username = self.other_user.username,
-                password = self.other_user.username)
-        response = self.client.post(bundle_url(self.bundle), data)
-        self.bundle = Bundle.objects.get(pk = self.bundle.pk)
-        self.assertNotEqual(self.bundle.name, newname)
-
-class BundleCreateFromListTest(BundleTestBase):
-    def testCreateEmptyBundle(self):
-        newbundlename = 'testbundle-new'
-        params = {'form': 'patchlistform',
-                  'bundle_name': newbundlename,
-                  'action': 'Create',
-                  'project': defaults.project.id}
-
-        response = self.client.post(
-                '/project/%s/list/' % defaults.project.linkname,
-                params)
-
-        self.assertContains(response, 'Bundle %s created' % newbundlename)
-
-    def testCreateNonEmptyBundle(self):
-        newbundlename = 'testbundle-new'
-        patch = self.patches[0]
-
-        params = {'form': 'patchlistform',
-                  'bundle_name': newbundlename,
-                  'action': 'Create',
-                  'project': defaults.project.id,
-                  'patch_id:%d' % patch.id: 'checked'}
-
-        response = self.client.post(
-                '/project/%s/list/' % defaults.project.linkname,
-                params)
-
-        self.assertContains(response, 'Bundle %s created' % newbundlename)
-        self.assertContains(response, 'added to bundle %s' % newbundlename,
-            count = 1)
-
-        bundle = Bundle.objects.get(name = newbundlename)
-        self.failUnlessEqual(bundle.patches.count(), 1)
-        self.failUnlessEqual(bundle.patches.all()[0], patch)
-
-    def testCreateNonEmptyBundleEmptyName(self):
-        newbundlename = 'testbundle-new'
-        patch = self.patches[0]
-
-        n_bundles = Bundle.objects.count()
-
-        params = {'form': 'patchlistform',
-                  'bundle_name': '',
-                  'action': 'Create',
-                  'project': defaults.project.id,
-                  'patch_id:%d' % patch.id: 'checked'}
-
-        response = self.client.post(
-                '/project/%s/list/' % defaults.project.linkname,
-                params)
-
-        self.assertContains(response, 'No bundle name was specified',
-                status_code = 200)
-
-        # test that no new bundles are present
-        self.failUnlessEqual(n_bundles, Bundle.objects.count())
-
-    def testCreateDuplicateName(self):
-        newbundlename = 'testbundle-dup'
-        patch = self.patches[0]
-
-        params = {'form': 'patchlistform',
-                  'bundle_name': newbundlename,
-                  'action': 'Create',
-                  'project': defaults.project.id,
-                  'patch_id:%d' % patch.id: 'checked'}
-
-        response = self.client.post(
-                '/project/%s/list/' % defaults.project.linkname,
-                params)
-
-        n_bundles = Bundle.objects.count()
-        self.assertContains(response, 'Bundle %s created' % newbundlename)
-        self.assertContains(response, 'added to bundle %s' % newbundlename,
-            count = 1)
-
-        bundle = Bundle.objects.get(name = newbundlename)
-        self.failUnlessEqual(bundle.patches.count(), 1)
-        self.failUnlessEqual(bundle.patches.all()[0], patch)
-
-        response = self.client.post(
-                '/project/%s/list/' % defaults.project.linkname,
-                params)
-
-        self.assertNotContains(response, 'Bundle %s created' % newbundlename)
-        self.assertContains(response, 'You already have a bundle called')
-        self.assertEqual(Bundle.objects.count(), n_bundles)
-        self.assertEqual(bundle.patches.count(), 1)
-
-class BundleCreateFromPatchTest(BundleTestBase):
-    def testCreateNonEmptyBundle(self):
-        newbundlename = 'testbundle-new'
-        patch = self.patches[0]
-
-        params = {'name': newbundlename,
-                  'action': 'createbundle'}
-
-        response = self.client.post('/patch/%d/' % patch.id, params)
-
-        self.assertContains(response,
-                'Bundle %s created' % newbundlename)
-
-        bundle = Bundle.objects.get(name = newbundlename)
-        self.failUnlessEqual(bundle.patches.count(), 1)
-        self.failUnlessEqual(bundle.patches.all()[0], patch)
-
-    def testCreateWithExistingName(self):
-        newbundlename = self.bundle.name
-        patch = self.patches[0]
-
-        params = {'name': newbundlename,
-                  'action': 'createbundle'}
-
-        response = self.client.post('/patch/%d/' % patch.id, params)
-
-        self.assertContains(response,
-                'A bundle called %s already exists' % newbundlename)
-
-        count = Bundle.objects.count()
-        self.failUnlessEqual(Bundle.objects.count(), 1)
-
-class BundleAddFromListTest(BundleTestBase):
-    def testAddToEmptyBundle(self):
-        patch = self.patches[0]
-        params = {'form': 'patchlistform',
-                  'action': 'Add',
-                  'project': defaults.project.id,
-                  'bundle_id': self.bundle.id,
-                  'patch_id:%d' % patch.id: 'checked'}
-
-        response = self.client.post(
-                '/project/%s/list/' % defaults.project.linkname,
-                params)
-
-        self.assertContains(response, 'added to bundle %s' % self.bundle.name,
-            count = 1)
-
-        self.failUnlessEqual(self.bundle.patches.count(), 1)
-        self.failUnlessEqual(self.bundle.patches.all()[0], patch)
-
-    def testAddToNonEmptyBundle(self):
-        self.bundle.append_patch(self.patches[0])
-        patch = self.patches[1]
-        params = {'form': 'patchlistform',
-                  'action': 'Add',
-                  'project': defaults.project.id,
-                  'bundle_id': self.bundle.id,
-                  'patch_id:%d' % patch.id: 'checked'}
-
-        response = self.client.post(
-                '/project/%s/list/' % defaults.project.linkname,
-                params)
-
-        self.assertContains(response, 'added to bundle %s' % self.bundle.name,
-            count = 1)
-
-        self.failUnlessEqual(self.bundle.patches.count(), 2)
-        self.failUnless(self.patches[0] in self.bundle.patches.all())
-        self.failUnless(self.patches[1] in self.bundle.patches.all())
-
-        # check order
-        bps = [ BundlePatch.objects.get(bundle = self.bundle,
-                                        patch = self.patches[i]) \
-                for i in [0, 1] ]
-        self.failUnless(bps[0].order < bps[1].order)
-
-    def testAddDuplicate(self):
-        self.bundle.append_patch(self.patches[0])
-        count = self.bundle.patches.count()
-        patch = self.patches[0]
-
-        params = {'form': 'patchlistform',
-                  'action': 'Add',
-                  'project': defaults.project.id,
-                  'bundle_id': self.bundle.id,
-                  'patch_id:%d' % patch.id: 'checked'}
-
-        response = self.client.post(
-                '/project/%s/list/' % defaults.project.linkname,
-                params)
-
-        self.assertContains(response, 'Patch &#39;%s&#39; already in bundle' \
-                            % patch.name, count = 1, status_code = 200)
-
-        self.assertEquals(count, self.bundle.patches.count())
-
-    def testAddNewAndDuplicate(self):
-        self.bundle.append_patch(self.patches[0])
-        count = self.bundle.patches.count()
-        patch = self.patches[0]
-
-        params = {'form': 'patchlistform',
-                  'action': 'Add',
-                  'project': defaults.project.id,
-                  'bundle_id': self.bundle.id,
-                  'patch_id:%d' % patch.id: 'checked',
-                  'patch_id:%d' % self.patches[1].id: 'checked'}
-
-        response = self.client.post(
-                '/project/%s/list/' % defaults.project.linkname,
-                params)
-
-        self.assertContains(response, 'Patch &#39;%s&#39; already in bundle' \
-                            % patch.name, count = 1, status_code = 200)
-        self.assertContains(response, 'Patch &#39;%s&#39; added to bundle' \
-                            % self.patches[1].name, count = 1,
-                            status_code = 200)
-        self.assertEquals(count + 1, self.bundle.patches.count())
-
-class BundleAddFromPatchTest(BundleTestBase):
-    def testAddToEmptyBundle(self):
-        patch = self.patches[0]
-        params = {'action': 'addtobundle',
-                  'bundle_id': self.bundle.id}
-
-        response = self.client.post('/patch/%d/' % patch.id, params)
-
-        self.assertContains(response,
-                'added to bundle &quot;%s&quot;' % self.bundle.name,
-                count = 1)
-
-        self.failUnlessEqual(self.bundle.patches.count(), 1)
-        self.failUnlessEqual(self.bundle.patches.all()[0], patch)
-
-    def testAddToNonEmptyBundle(self):
-        self.bundle.append_patch(self.patches[0])
-        patch = self.patches[1]
-        params = {'action': 'addtobundle',
-                  'bundle_id': self.bundle.id}
-
-        response = self.client.post('/patch/%d/' % patch.id, params)
-
-        self.assertContains(response,
-                'added to bundle &quot;%s&quot;' % self.bundle.name,
-                count = 1)
-
-        self.failUnlessEqual(self.bundle.patches.count(), 2)
-        self.failUnless(self.patches[0] in self.bundle.patches.all())
-        self.failUnless(self.patches[1] in self.bundle.patches.all())
-
-        # check order
-        bps = [ BundlePatch.objects.get(bundle = self.bundle,
-                                        patch = self.patches[i]) \
-                for i in [0, 1] ]
-        self.failUnless(bps[0].order < bps[1].order)
-
-class BundleInitialOrderTest(BundleTestBase):
-    """When creating bundles from a patch list, ensure that the patches in the
-       bundle are ordered by date"""
-
-    def setUp(self):
-        super(BundleInitialOrderTest, self).setUp(5)
-
-        # put patches in an arbitrary order
-        idxs = [2, 4, 3, 1, 0]
-        self.patches = [ self.patches[i] for i in idxs ]
-
-        # set dates to be sequential
-        last_patch = self.patches[0]
-        for patch in self.patches[1:]:
-            patch.date = last_patch.date + datetime.timedelta(0, 1)
-            patch.save()
-            last_patch = patch
-
-    def _testOrder(self, ids, expected_order):
-        newbundlename = 'testbundle-new'
-
-        # need to define our querystring explicity to enforce ordering
-        params = {'form': 'patchlistform',
-                  'bundle_name': newbundlename,
-                  'action': 'Create',
-                  'project': defaults.project.id,
-        }
-
-        data = urlencode(params) + \
-               ''.join([ '&patch_id:%d=checked' % i for i in ids ])
-
-        response = self.client.post(
-                '/project/%s/list/' % defaults.project.linkname,
-                data = data,
-                content_type = 'application/x-www-form-urlencoded',
-                )
-
-        self.assertContains(response, 'Bundle %s created' % newbundlename)
-        self.assertContains(response, 'added to bundle %s' % newbundlename,
-            count = 5)
-
-        bundle = Bundle.objects.get(name = newbundlename)
-
-        # BundlePatches should be sorted by .order by default
-        bps = BundlePatch.objects.filter(bundle = bundle)
-
-        for (bp, p) in zip(bps, expected_order):
-            self.assertEqual(bp.patch.pk, p.pk)
-
-        bundle.delete()
-
-    def testBundleForwardOrder(self):
-        ids = map(lambda p: p.id, self.patches)
-        self._testOrder(ids, self.patches)
-
-    def testBundleReverseOrder(self):
-        ids = map(lambda p: p.id, self.patches)
-        ids.reverse()
-        self._testOrder(ids, self.patches)
-
-class BundleReorderTest(BundleTestBase):
-    def setUp(self):
-        super(BundleReorderTest, self).setUp(5)
-        for i in range(5):
-            self.bundle.append_patch(self.patches[i])
-
-    def checkReordering(self, neworder, start, end):
-        neworder_ids = [ self.patches[i].id for i in neworder ]
-
-        firstpatch = BundlePatch.objects.get(bundle = self.bundle,
-                patch = self.patches[start]).patch
-
-        slice_ids = neworder_ids[start:end]
-        params = {'form': 'reorderform',
-                  'order_start': firstpatch.id,
-                  'neworder': slice_ids}
-
-        response = self.client.post(bundle_url(self.bundle), params)
-
-        self.failUnlessEqual(response.status_code, 200)
-
-        bps = BundlePatch.objects.filter(bundle = self.bundle) \
-                        .order_by('order')
-
-        # check if patch IDs are in the expected order:
-        bundle_ids = [ bp.patch.id for bp in bps ]
-        self.failUnlessEqual(neworder_ids, bundle_ids)
-
-        # check if order field is still sequential:
-        order_numbers = [ bp.order for bp in bps ]
-        expected_order = range(1, len(neworder)+1) # [1 ... len(neworder)]
-        self.failUnlessEqual(order_numbers, expected_order)
-
-    def testBundleReorderAll(self):
-        # reorder all patches:
-        self.checkReordering([2,1,4,0,3], 0, 5)
-
-    def testBundleReorderEnd(self):
-        # reorder only the last three patches
-        self.checkReordering([0,1,3,2,4], 2, 5)
-
-    def testBundleReorderBegin(self):
-        # reorder only the first three patches
-        self.checkReordering([2,0,1,3,4], 0, 3)
-
-    def testBundleReorderMiddle(self):
-        # reorder only 2nd, 3rd, and 4th patches
-        self.checkReordering([0,2,3,1,4], 1, 4)
-
-class BundleRedirTest(BundleTestBase):
-    # old URL: private bundles used to be under /user/bundle/<id>
-
-    def setUp(self):
-        super(BundleRedirTest, self).setUp()
-
-    @unittest.skipIf(not settings.COMPAT_REDIR, "compat redirections disabled")
-    def testBundleRedir(self):
-        url = '/user/bundle/%d/' % self.bundle.id
-        response = self.client.get(url)
-        self.assertRedirects(response, bundle_url(self.bundle))
-
-    @unittest.skipIf(not settings.COMPAT_REDIR, "compat redirections disabled")
-    def testMboxRedir(self):
-        url = '/user/bundle/%d/mbox/' % self.bundle.id
-        response = self.client.get(url)
-        self.assertRedirects(response,'/bundle/%s/%s/mbox/' %
-                                        (self.bundle.owner.username,
-                                         self.bundle.name))
diff --git a/apps/patchwork/tests/confirm.py b/apps/patchwork/tests/confirm.py
deleted file mode 100644 (file)
index fad5125..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2011 Jeremy Kerr <jk@ozlabs.org>
-#
-# This file is part of the Patchwork package.
-#
-# Patchwork is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# Patchwork is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with Patchwork; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-import unittest
-from django.test import TestCase
-from django.contrib.auth.models import User
-from django.core.urlresolvers import reverse
-from patchwork.models import EmailConfirmation, Person
-
-def _confirmation_url(conf):
-    return reverse('patchwork.views.confirm', kwargs = {'key': conf.key})
-
-class TestUser(object):
-    username = 'testuser'
-    email = 'test@example.com'
-    secondary_email = 'test2@example.com'
-    password = None
-
-    def __init__(self):
-        self.password = User.objects.make_random_password()
-        self.user = User.objects.create_user(self.username,
-                            self.email, self.password)
-
-class InvalidConfirmationTest(TestCase):
-    def setUp(self):
-        EmailConfirmation.objects.all().delete()
-        Person.objects.all().delete()
-        self.user = TestUser()
-        self.conf = EmailConfirmation(type = 'userperson',
-                                      email = self.user.secondary_email,
-                                      user = self.user.user)
-        self.conf.save()
-
-    def testInactiveConfirmation(self):
-        self.conf.active = False
-        self.conf.save()
-        response = self.client.get(_confirmation_url(self.conf))
-        self.assertEquals(response.status_code, 200)
-        self.assertTemplateUsed(response, 'patchwork/confirm-error.html')
-        self.assertEqual(response.context['error'], 'inactive')
-        self.assertEqual(response.context['conf'], self.conf)
-
-    def testExpiredConfirmation(self):
-        self.conf.date -= self.conf.validity
-        self.conf.save()
-        response = self.client.get(_confirmation_url(self.conf))
-        self.assertEquals(response.status_code, 200)
-        self.assertTemplateUsed(response, 'patchwork/confirm-error.html')
-        self.assertEqual(response.context['error'], 'expired')
-        self.assertEqual(response.context['conf'], self.conf)
-
diff --git a/apps/patchwork/tests/encodings.py b/apps/patchwork/tests/encodings.py
deleted file mode 100644 (file)
index b9032bb..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
-#
-# This file is part of the Patchwork package.
-#
-# Patchwork is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# Patchwork is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with Patchwork; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-import unittest
-import os
-import time
-from patchwork.models import Patch, Person
-from patchwork.tests.utils import defaults, read_patch
-from django.test import TestCase
-from django.test.client import Client
-
-class UTF8PatchViewTest(TestCase):
-    patch_filename = '0002-utf-8.patch'
-    patch_encoding = 'utf-8'
-
-    def setUp(self):
-        defaults.project.save()
-        defaults.patch_author_person.save()
-        self.patch_content = read_patch(self.patch_filename,
-                encoding = self.patch_encoding)
-        self.patch = Patch(project = defaults.project,
-                           msgid = 'x', name = defaults.patch_name,
-                           submitter = defaults.patch_author_person,
-                           content = self.patch_content)
-        self.patch.save()
-        self.client = Client()
-
-    def testPatchView(self):
-        response = self.client.get('/patch/%d/' % self.patch.id)
-        self.assertContains(response, self.patch.name)
-
-    def testMboxView(self):
-        response = self.client.get('/patch/%d/mbox/' % self.patch.id)
-        self.assertEquals(response.status_code, 200)
-        self.assertTrue(self.patch.content in \
-                response.content.decode(self.patch_encoding))
-
-    def testRawView(self):
-        response = self.client.get('/patch/%d/raw/' % self.patch.id)
-        self.assertEquals(response.status_code, 200)
-        self.assertEquals(response.content.decode(self.patch_encoding),
-                self.patch.content)
-
-    def tearDown(self):
-        self.patch.delete()
-        defaults.patch_author_person.delete()
-        defaults.project.delete()
-
-class UTF8HeaderPatchViewTest(UTF8PatchViewTest):
-    patch_filename = '0002-utf-8.patch'
-    patch_encoding = 'utf-8'
-    patch_author_name = u'P\xe4tch Author'
-
-    def setUp(self):
-        defaults.project.save()
-        self.patch_author = Person(name = self.patch_author_name,
-            email = defaults.patch_author_person.email)
-        self.patch_author.save()
-        self.patch_content = read_patch(self.patch_filename,
-                encoding = self.patch_encoding)
-        self.patch = Patch(project = defaults.project,
-                           msgid = 'x', name = defaults.patch_name,
-                           submitter = self.patch_author,
-                           content = self.patch_content)
-        self.patch.save()
-        self.client = Client()
-
-    def tearDown(self):
-        self.patch.delete()
-        self.patch_author.delete()
-        defaults.project.delete()
diff --git a/apps/patchwork/tests/expiry.py b/apps/patchwork/tests/expiry.py
deleted file mode 100644 (file)
index 844ed4b..0000000
+++ /dev/null
@@ -1,121 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2014 Jeremy Kerr <jk@ozlabs.org>
-#
-# This file is part of the Patchwork package.
-#
-# Patchwork is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# Patchwork is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with Patchwork; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-import unittest
-import datetime
-from django.test import TestCase
-from django.contrib.auth.models import User
-from patchwork.models import EmailConfirmation, Person, Patch
-from patchwork.tests.utils import create_user, defaults
-from patchwork.utils import do_expiry
-
-class TestRegistrationExpiry(TestCase):
-
-    def register(self, date):
-        user = create_user()
-        user.is_active = False
-        user.date_joined = user.last_login = date
-        user.save()
-
-        conf = EmailConfirmation(type='registration', user=user,
-                                    email=user.email)
-        conf.date = date
-        conf.save()
-
-        return (user, conf)
-
-    def testOldRegistrationExpiry(self):
-        date = ((datetime.datetime.now() - EmailConfirmation.validity) -
-                datetime.timedelta(hours = 1))
-        (user, conf) = self.register(date)
-
-        do_expiry()
-
-        self.assertFalse(User.objects.filter(pk = user.pk).exists())
-        self.assertFalse(EmailConfirmation.objects.filter(pk = conf.pk)
-                            .exists())
-
-
-    def testRecentRegistrationExpiry(self):
-        date = ((datetime.datetime.now() - EmailConfirmation.validity) +
-                datetime.timedelta(hours = 1))
-        (user, conf) = self.register(date)
-
-        do_expiry()
-
-        self.assertTrue(User.objects.filter(pk = user.pk).exists())
-        self.assertTrue(EmailConfirmation.objects.filter(pk = conf.pk)
-                            .exists())
-
-    def testInactiveRegistrationExpiry(self):
-        (user, conf) = self.register(datetime.datetime.now())
-
-        # confirm registration
-        conf.user.is_active = True
-        conf.user.save()
-        conf.deactivate()
-
-        do_expiry()
-
-        self.assertTrue(User.objects.filter(pk = user.pk).exists())
-        self.assertFalse(EmailConfirmation.objects.filter(pk = conf.pk)
-                            .exists())
-
-    def testPatchSubmitterExpiry(self):
-        defaults.project.save()
-        defaults.patch_author_person.save()
-
-        # someone submits a patch...
-        patch = Patch(project = defaults.project,
-                    msgid = 'test@example.com', name = 'test patch',
-                    submitter = defaults.patch_author_person,
-                    content = defaults.patch)
-        patch.save()
-
-        # ... then starts registration...
-        date = ((datetime.datetime.now() - EmailConfirmation.validity) -
-                datetime.timedelta(hours = 1))
-        userid = 'test-user'
-        user = User.objects.create_user(userid,
-                defaults.patch_author_person.email, userid)
-        user.is_active = False
-        user.date_joined = user.last_login = date
-        user.save()
-
-        self.assertEqual(user.email, patch.submitter.email)
-
-        conf = EmailConfirmation(type='registration', user=user,
-                                    email=user.email)
-        conf.date = date
-        conf.save()
-
-        # ... which expires
-        do_expiry()
-
-        # we should see no matching user
-        self.assertFalse(User.objects.filter(email = patch.submitter.email)
-                            .exists())
-        # but the patch and person should still be present
-        self.assertTrue(Person.objects.filter(
-                            pk = defaults.patch_author_person.pk).exists())
-        self.assertTrue(Patch.objects.filter(pk = patch.pk).exists())
-
-        # and there should be no user associated with the person
-        self.assertEqual(Person.objects.get(pk =
-                    defaults.patch_author_person.pk).user, None)
diff --git a/apps/patchwork/tests/filters.py b/apps/patchwork/tests/filters.py
deleted file mode 100644 (file)
index 2c464e5..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2011 Jeremy Kerr <jk@ozlabs.org>
-#
-# This file is part of the Patchwork package.
-#
-# Patchwork is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# Patchwork is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with Patchwork; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-import unittest
-from django.test import TestCase
-from django.test.client import Client
-from patchwork.tests.utils import defaults, create_user, find_in_context
-
-class FilterQueryStringTest(TestCase):
-    def testFilterQSEscaping(self):
-        """test that filter fragments in a query string are properly escaped,
-           and stray ampersands don't get reflected back in the filter
-           links"""
-        project = defaults.project
-        defaults.project.save()
-        url = '/project/%s/list/?submitter=a%%26b=c' % project.linkname
-        response = self.client.get(url)
-        self.failUnlessEqual(response.status_code, 200)
-        self.failIf('submitter=a&amp;b=c' in response.content)
-        self.failIf('submitter=a&b=c' in response.content)
-
-    def testUTF8QSHandling(self):
-        """test that non-ascii characters can be handled by the filter
-           code"""
-        project = defaults.project
-        defaults.project.save()
-        url = '/project/%s/list/?submitter=%%E2%%98%%83' % project.linkname
-        response = self.client.get(url)
-        self.failUnlessEqual(response.status_code, 200)
diff --git a/apps/patchwork/tests/list.py b/apps/patchwork/tests/list.py
deleted file mode 100644 (file)
index 1bdb506..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2012 Jeremy Kerr <jk@ozlabs.org>
-#
-# This file is part of the Patchwork package.
-#
-# Patchwork is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# Patchwork is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with Patchwork; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-import unittest
-from django.test import TestCase
-from django.test.client import Client
-from patchwork.tests.utils import defaults, create_user, find_in_context
-from django.core.urlresolvers import reverse
-
-class EmptyPatchListTest(TestCase):
-
-    def testEmptyPatchList(self):
-        """test that we don't output an empty table when there are no
-           patches present"""
-        project = defaults.project
-        defaults.project.save()
-        url = reverse('patchwork.views.patch.list',
-                kwargs={'project_id': project.linkname})
-        response = self.client.get(url)
-        self.assertContains(response, 'No patches to display')
-        self.assertNotContains(response, 'tbody')
-
diff --git a/apps/patchwork/tests/mail_settings.py b/apps/patchwork/tests/mail_settings.py
deleted file mode 100644 (file)
index a193c97..0000000
+++ /dev/null
@@ -1,299 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2010 Jeremy Kerr <jk@ozlabs.org>
-#
-# This file is part of the Patchwork package.
-#
-# Patchwork is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# Patchwork is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with Patchwork; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-import unittest
-import re
-from django.test import TestCase
-from django.test.client import Client
-from django.core import mail
-from django.core.urlresolvers import reverse
-from django.contrib.auth.models import User
-from patchwork.models import EmailOptout, EmailConfirmation, Person
-from patchwork.tests.utils import create_user, error_strings
-
-class MailSettingsTest(TestCase):
-    view = 'patchwork.views.mail.settings'
-    url = reverse(view)
-
-    def testMailSettingsGET(self):
-        response = self.client.get(self.url)
-        self.assertEquals(response.status_code, 200)
-        self.assertTrue(response.context['form'])
-
-    def testMailSettingsPOST(self):
-        email = u'foo@example.com'
-        response = self.client.post(self.url, {'email': email})
-        self.assertEquals(response.status_code, 200)
-        self.assertTemplateUsed(response, 'patchwork/mail-settings.html')
-        self.assertEquals(response.context['email'], email)
-
-    def testMailSettingsPOSTEmpty(self):
-        response = self.client.post(self.url, {'email': ''})
-        self.assertEquals(response.status_code, 200)
-        self.assertTemplateUsed(response, 'patchwork/mail-form.html')
-        self.assertFormError(response, 'form', 'email',
-                'This field is required.')
-
-    def testMailSettingsPOSTInvalid(self):
-        response = self.client.post(self.url, {'email': 'foo'})
-        self.assertEquals(response.status_code, 200)
-        self.assertTemplateUsed(response, 'patchwork/mail-form.html')
-        self.assertFormError(response, 'form', 'email', error_strings['email'])
-
-    def testMailSettingsPOSTOptedIn(self):
-        email = u'foo@example.com'
-        response = self.client.post(self.url, {'email': email})
-        self.assertEquals(response.status_code, 200)
-        self.assertTemplateUsed(response, 'patchwork/mail-settings.html')
-        self.assertEquals(response.context['is_optout'], False)
-        self.assertTrue('<strong>may</strong>' in response.content)
-        optout_url = reverse('patchwork.views.mail.optout')
-        self.assertTrue(('action="%s"' % optout_url) in response.content)
-
-    def testMailSettingsPOSTOptedOut(self):
-        email = u'foo@example.com'
-        EmailOptout(email = email).save()
-        response = self.client.post(self.url, {'email': email})
-        self.assertEquals(response.status_code, 200)
-        self.assertTemplateUsed(response, 'patchwork/mail-settings.html')
-        self.assertEquals(response.context['is_optout'], True)
-        self.assertTrue('<strong>may not</strong>' in response.content)
-        optin_url = reverse('patchwork.views.mail.optin')
-        self.assertTrue(('action="%s"' % optin_url) in response.content)
-
-class OptoutRequestTest(TestCase):
-    view = 'patchwork.views.mail.optout'
-    url = reverse(view)
-
-    def testOptOutRequestGET(self):
-        response = self.client.get(self.url)
-        self.assertRedirects(response, reverse('patchwork.views.mail.settings'))
-
-    def testOptoutRequestValidPOST(self):
-        email = u'foo@example.com'
-        response = self.client.post(self.url, {'email': email})
-
-        # check for a confirmation object
-        self.assertEquals(EmailConfirmation.objects.count(), 1)
-        conf = EmailConfirmation.objects.get(email = email)
-
-        # check confirmation page
-        self.assertEquals(response.status_code, 200)
-        self.assertEquals(response.context['confirmation'], conf)
-        self.assertTrue(email in response.content)
-
-        # check email
-        url = reverse('patchwork.views.confirm', kwargs = {'key': conf.key})
-        self.assertEquals(len(mail.outbox), 1)
-        msg = mail.outbox[0]
-        self.assertEquals(msg.to, [email])
-        self.assertEquals(msg.subject, 'Patchwork opt-out confirmation')
-        self.assertTrue(url in msg.body)
-
-    def testOptoutRequestInvalidPOSTEmpty(self):
-        response = self.client.post(self.url, {'email': ''})
-        self.assertEquals(response.status_code, 200)
-        self.assertFormError(response, 'form', 'email',
-                'This field is required.')
-        self.assertTrue(response.context['error'])
-        self.assertTrue('email_sent' not in response.context)
-        self.assertEquals(len(mail.outbox), 0)
-
-    def testOptoutRequestInvalidPOSTNonEmail(self):
-        response = self.client.post(self.url, {'email': 'foo'})
-        self.assertEquals(response.status_code, 200)
-        self.assertFormError(response, 'form', 'email', error_strings['email'])
-        self.assertTrue(response.context['error'])
-        self.assertTrue('email_sent' not in response.context)
-        self.assertEquals(len(mail.outbox), 0)
-
-class OptoutTest(TestCase):
-    view = 'patchwork.views.mail.optout'
-    url = reverse(view)
-
-    def setUp(self):
-        self.email = u'foo@example.com'
-        self.conf = EmailConfirmation(type = 'optout', email = self.email)
-        self.conf.save()
-
-    def testOptoutValidHash(self):
-        url = reverse('patchwork.views.confirm',
-                        kwargs = {'key': self.conf.key})
-        response = self.client.get(url)
-
-        self.assertEquals(response.status_code, 200)
-        self.assertTemplateUsed(response, 'patchwork/optout.html')
-        self.assertTrue(self.email in response.content)
-
-        # check that we've got an optout in the list
-        self.assertEquals(EmailOptout.objects.count(), 1)
-        self.assertEquals(EmailOptout.objects.all()[0].email, self.email)
-
-        # check that the confirmation is now inactive
-        self.assertFalse(EmailConfirmation.objects.get(
-                                    pk = self.conf.pk).active)
-
-
-class OptoutPreexistingTest(OptoutTest):
-    """Test that a duplicated opt-out behaves the same as the initial one"""
-    def setUp(self):
-        super(OptoutPreexistingTest, self).setUp()
-        EmailOptout(email = self.email).save()
-
-class OptinRequestTest(TestCase):
-    view = 'patchwork.views.mail.optin'
-    url = reverse(view)
-
-    def setUp(self):
-        self.email = u'foo@example.com'
-        EmailOptout(email = self.email).save()
-
-    def testOptInRequestGET(self):
-        response = self.client.get(self.url)
-        self.assertRedirects(response, reverse('patchwork.views.mail.settings'))
-
-    def testOptInRequestValidPOST(self):
-        response = self.client.post(self.url, {'email': self.email})
-
-        # check for a confirmation object
-        self.assertEquals(EmailConfirmation.objects.count(), 1)
-        conf = EmailConfirmation.objects.get(email = self.email)
-
-        # check confirmation page
-        self.assertEquals(response.status_code, 200)
-        self.assertEquals(response.context['confirmation'], conf)
-        self.assertTrue(self.email in response.content)
-
-        # check email
-        url = reverse('patchwork.views.confirm', kwargs = {'key': conf.key})
-        self.assertEquals(len(mail.outbox), 1)
-        msg = mail.outbox[0]
-        self.assertEquals(msg.to, [self.email])
-        self.assertEquals(msg.subject, 'Patchwork opt-in confirmation')
-        self.assertTrue(url in msg.body)
-
-    def testOptoutRequestInvalidPOSTEmpty(self):
-        response = self.client.post(self.url, {'email': ''})
-        self.assertEquals(response.status_code, 200)
-        self.assertFormError(response, 'form', 'email',
-                'This field is required.')
-        self.assertTrue(response.context['error'])
-        self.assertTrue('email_sent' not in response.context)
-        self.assertEquals(len(mail.outbox), 0)
-
-    def testOptoutRequestInvalidPOSTNonEmail(self):
-        response = self.client.post(self.url, {'email': 'foo'})
-        self.assertEquals(response.status_code, 200)
-        self.assertFormError(response, 'form', 'email', error_strings['email'])
-        self.assertTrue(response.context['error'])
-        self.assertTrue('email_sent' not in response.context)
-        self.assertEquals(len(mail.outbox), 0)
-
-class OptinTest(TestCase):
-
-    def setUp(self):
-        self.email = u'foo@example.com'
-        self.optout = EmailOptout(email = self.email)
-        self.optout.save()
-        self.conf = EmailConfirmation(type = 'optin', email = self.email)
-        self.conf.save()
-
-    def testOptinValidHash(self):
-        url = reverse('patchwork.views.confirm',
-                        kwargs = {'key': self.conf.key})
-        response = self.client.get(url)
-
-        self.assertEquals(response.status_code, 200)
-        self.assertTemplateUsed(response, 'patchwork/optin.html')
-        self.assertTrue(self.email in response.content)
-
-        # check that there's no optout remaining
-        self.assertEquals(EmailOptout.objects.count(), 0)
-
-        # check that the confirmation is now inactive
-        self.assertFalse(EmailConfirmation.objects.get(
-                                    pk = self.conf.pk).active)
-
-class OptinWithoutOptoutTest(TestCase):
-    """Test an opt-in with no existing opt-out"""
-    view = 'patchwork.views.mail.optin'
-    url = reverse(view)
-
-    def testOptInWithoutOptout(self):
-        email = u'foo@example.com'
-        response = self.client.post(self.url, {'email': email})
-
-        # check for an error message
-        self.assertEquals(response.status_code, 200)
-        self.assertTrue(bool(response.context['error']))
-        self.assertTrue('not on the patchwork opt-out list' in response.content)
-
-class UserProfileOptoutFormTest(TestCase):
-    """Test that the correct optin/optout forms appear on the user profile
-       page, for logged-in users"""
-
-    view = 'patchwork.views.user.profile'
-    url = reverse(view)
-    optout_url = reverse('patchwork.views.mail.optout')
-    optin_url = reverse('patchwork.views.mail.optin')
-    form_re_template = ('<form\s+[^>]*action="%(url)s"[^>]*>'
-                        '.*?<input\s+[^>]*value="%(email)s"[^>]*>.*?'
-                        '</form>')
-    secondary_email = 'test2@example.com'
-
-    def setUp(self):
-        self.user = create_user()
-        self.client.login(username = self.user.username,
-                password = self.user.username)
-
-    def _form_re(self, url, email):
-        return re.compile(self.form_re_template % {'url': url, 'email': email},
-                          re.DOTALL)
-
-    def testMainEmailOptoutForm(self):
-        form_re = self._form_re(self.optout_url, self.user.email)
-        response = self.client.get(self.url)
-        self.assertEquals(response.status_code, 200)
-        self.assertTrue(form_re.search(response.content) is not None)
-
-    def testMainEmailOptinForm(self):
-        EmailOptout(email = self.user.email).save()
-        form_re = self._form_re(self.optin_url, self.user.email)
-        response = self.client.get(self.url)
-        self.assertEquals(response.status_code, 200)
-        self.assertTrue(form_re.search(response.content) is not None)
-
-    def testSecondaryEmailOptoutForm(self):
-        p = Person(email = self.secondary_email, user = self.user)
-        p.save()
-        
-        form_re = self._form_re(self.optout_url, p.email)
-        response = self.client.get(self.url)
-        self.assertEquals(response.status_code, 200)
-        self.assertTrue(form_re.search(response.content) is not None)
-
-    def testSecondaryEmailOptinForm(self):
-        p = Person(email = self.secondary_email, user = self.user)
-        p.save()
-        EmailOptout(email = p.email).save()
-
-        form_re = self._form_re(self.optin_url, p.email)
-        response = self.client.get(self.url)
-        self.assertEquals(response.status_code, 200)
-        self.assertTrue(form_re.search(response.content) is not None)
diff --git a/apps/patchwork/tests/mboxviews.py b/apps/patchwork/tests/mboxviews.py
deleted file mode 100644 (file)
index 6209513..0000000
+++ /dev/null
@@ -1,181 +0,0 @@
-# vim: set fileencoding=utf-8 :
-#
-# Patchwork - automated patch tracking system
-# Copyright (C) 2009 Jeremy Kerr <jk@ozlabs.org>
-#
-# This file is part of the Patchwork package.
-#
-# Patchwork is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# Patchwork is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with Patchwork; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-import unittest
-import email
-import datetime
-import dateutil.parser, dateutil.tz
-from django.test import TestCase
-from django.test.client import Client
-from patchwork.models import Patch, Comment, Person
-from patchwork.tests.utils import defaults, create_user, find_in_context
-
-class MboxPatchResponseTest(TestCase):
-    """ Test that the mbox view appends the Acked-by from a patch comment """
-    def setUp(self):
-        defaults.project.save()
-
-        self.person = defaults.patch_author_person
-        self.person.save()
-
-        self.patch = Patch(project = defaults.project,
-                           msgid = 'p1', name = 'testpatch',
-                           submitter = self.person, content = '')
-        self.patch.save()
-        comment = Comment(patch = self.patch, msgid = 'p1',
-                submitter = self.person,
-                content = 'comment 1 text\nAcked-by: 1\n')
-        comment.save()
-
-        comment = Comment(patch = self.patch, msgid = 'p2',
-                submitter = self.person,
-                content = 'comment 2 text\nAcked-by: 2\n')
-        comment.save()
-
-    def testPatchResponse(self):
-        response = self.client.get('/patch/%d/mbox/' % self.patch.id)
-        self.assertContains(response,
-                'Acked-by: 1\nAcked-by: 2\n')
-
-class MboxPatchSplitResponseTest(TestCase):
-    """ Test that the mbox view appends the Acked-by from a patch comment,
-        and places it before an '---' update line. """
-    def setUp(self):
-        defaults.project.save()
-
-        self.person = defaults.patch_author_person
-        self.person.save()
-
-        self.patch = Patch(project = defaults.project,
-                           msgid = 'p1', name = 'testpatch',
-                           submitter = self.person, content = '')
-        self.patch.save()
-        comment = Comment(patch = self.patch, msgid = 'p1',
-                submitter = self.person,
-                content = 'comment 1 text\nAcked-by: 1\n---\nupdate\n')
-        comment.save()
-
-        comment = Comment(patch = self.patch, msgid = 'p2',
-                submitter = self.person,
-                content = 'comment 2 text\nAcked-by: 2\n')
-        comment.save()
-
-    def testPatchResponse(self):
-        response = self.client.get('/patch/%d/mbox/' % self.patch.id)
-        self.assertContains(response,
-                'Acked-by: 1\nAcked-by: 2\n')
-
-class MboxPassThroughHeaderTest(TestCase):
-    """ Test that we see 'Cc' and 'To' headers passed through from original
-        message to mbox view """
-
-    def setUp(self):
-        defaults.project.save()
-        self.person = defaults.patch_author_person
-        self.person.save()
-
-        self.cc_header = 'Cc: CC Person <cc@example.com>'
-        self.to_header = 'To: To Person <to@example.com>'
-        self.date_header = 'Date: Fri, 7 Jun 2013 15:42:54 +1000'
-
-        self.patch = Patch(project = defaults.project,
-                           msgid = 'p1', name = 'testpatch',
-                           submitter = self.person, content = '')
-
-    def testCCHeader(self):
-        self.patch.headers = self.cc_header + '\n'
-        self.patch.save()
-
-        response = self.client.get('/patch/%d/mbox/' % self.patch.id)
-        self.assertContains(response, self.cc_header)
-
-    def testToHeader(self):
-        self.patch.headers = self.to_header + '\n'
-        self.patch.save()
-
-        response = self.client.get('/patch/%d/mbox/' % self.patch.id)
-        self.assertContains(response, self.to_header)
-
-    def testDateHeader(self):
-        self.patch.headers = self.date_header + '\n'
-        self.patch.save()
-
-        response = self.client.get('/patch/%d/mbox/' % self.patch.id)
-        self.assertContains(response, self.date_header)
-
-class MboxBrokenFromHeaderTest(TestCase):
-    """ Test that a person with characters outside ASCII in his name do
-        produce correct From header. As RFC 2822 state we must retain the
-        <user@doamin.tld> format for the mail while the name part may be coded
-        in some ways. """
-
-    def setUp(self):
-        defaults.project.save()
-        self.person = defaults.patch_author_person
-        self.person.name = u'©ool guÅ·'
-        self.person.save()
-
-        self.patch = Patch(project = defaults.project,
-                msgid = 'p1', name = 'testpatch',
-                submitter = self.person, content = '')
-
-    def testFromHeader(self):
-        self.patch.save()
-        from_email = '<' + self.person.email + '>'
-
-        response = self.client.get('/patch/%d/mbox/' % self.patch.id)
-        self.assertContains(response, from_email)
-
-class MboxDateHeaderTest(TestCase):
-    """ Test that the date provided in the patch mail view is correct """
-
-    def setUp(self):
-        defaults.project.save()
-        self.person = defaults.patch_author_person
-        self.person.save()
-
-        self.patch = Patch(project = defaults.project,
-                           msgid = 'p1', name = 'testpatch',
-                           submitter = self.person, content = '')
-        self.patch.save()
-
-    def testDateHeader(self):
-        response = self.client.get('/patch/%d/mbox/' % self.patch.id)
-        mail = email.message_from_string(response.content)
-        mail_date = dateutil.parser.parse(mail['Date'])
-        # patch dates are all in UTC
-        patch_date = self.patch.date.replace(tzinfo=dateutil.tz.tzutc(),
-                                            microsecond=0)
-        self.assertEqual(mail_date, patch_date)
-
-    def testSuppliedDateHeader(self):
-        hour_offset = 3
-        tz = dateutil.tz.tzoffset(None, hour_offset * 60 * 60)
-        date = datetime.datetime.utcnow() - datetime.timedelta(days = 1)
-        date = date.replace(tzinfo=tz, microsecond=0)
-
-        self.patch.headers = 'Date: %s\n' % date.strftime("%a, %d %b %Y %T %z")
-        self.patch.save()
-
-        response = self.client.get('/patch/%d/mbox/' % self.patch.id)
-        mail = email.message_from_string(response.content)
-        mail_date = dateutil.parser.parse(mail['Date'])
-        self.assertEqual(mail_date, date)
diff --git a/apps/patchwork/tests/notifications.py b/apps/patchwork/tests/notifications.py
deleted file mode 100644 (file)
index df7e3f9..0000000
+++ /dev/null
@@ -1,255 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2011 Jeremy Kerr <jk@ozlabs.org>
-#
-# This file is part of the Patchwork package.
-#
-# Patchwork is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# Patchwork is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with Patchwork; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-import datetime
-from django.test import TestCase
-from django.core.urlresolvers import reverse
-from django.core import mail
-from django.conf import settings
-from django.db.utils import IntegrityError
-from patchwork.models import Patch, State, PatchChangeNotification, EmailOptout
-from patchwork.tests.utils import defaults, create_maintainer
-from patchwork.utils import send_notifications
-
-class PatchNotificationModelTest(TestCase):
-    """Tests for the creation & update of the PatchChangeNotification model"""
-
-    def setUp(self):
-        self.project = defaults.project
-        self.project.send_notifications = True
-        self.project.save()
-        self.submitter = defaults.patch_author_person
-        self.submitter.save()
-        self.patch = Patch(project = self.project, msgid = 'testpatch',
-                        name = 'testpatch', content = '',
-                        submitter = self.submitter)
-
-    def tearDown(self):
-        self.patch.delete()
-        self.submitter.delete()
-        self.project.delete()
-
-    def testPatchCreation(self):
-        """Ensure we don't get a notification on create"""
-        self.patch.save()
-        self.assertEqual(PatchChangeNotification.objects.count(), 0)
-
-    def testPatchUninterestingChange(self):
-        """Ensure we don't get a notification for "uninteresting" changes"""
-        self.patch.save()
-        self.patch.archived = True
-        self.patch.save()
-        self.assertEqual(PatchChangeNotification.objects.count(), 0)
-
-    def testPatchChange(self):
-        """Ensure we get a notification for interesting patch changes"""
-        self.patch.save()
-        oldstate = self.patch.state
-        state = State.objects.exclude(pk = oldstate.pk)[0]
-
-        self.patch.state = state
-        self.patch.save()
-        self.assertEqual(PatchChangeNotification.objects.count(), 1)
-        notification = PatchChangeNotification.objects.all()[0]
-        self.assertEqual(notification.patch, self.patch)
-        self.assertEqual(notification.orig_state, oldstate)
-
-    def testNotificationCancelled(self):
-        """Ensure we cancel notifications that are no longer valid"""
-        self.patch.save()
-        oldstate = self.patch.state
-        state = State.objects.exclude(pk = oldstate.pk)[0]
-
-        self.patch.state = state
-        self.patch.save()
-        self.assertEqual(PatchChangeNotification.objects.count(), 1)
-
-        self.patch.state = oldstate
-        self.patch.save()
-        self.assertEqual(PatchChangeNotification.objects.count(), 0)
-
-    def testNotificationUpdated(self):
-        """Ensure we update notifications when the patch has a second change,
-           but keep the original patch details"""
-        self.patch.save()
-        oldstate = self.patch.state
-        newstates = State.objects.exclude(pk = oldstate.pk)[:2]
-
-        self.patch.state = newstates[0]
-        self.patch.save()
-        self.assertEqual(PatchChangeNotification.objects.count(), 1)
-        notification = PatchChangeNotification.objects.all()[0]
-        self.assertEqual(notification.orig_state, oldstate)
-        orig_timestamp = notification.last_modified
-                         
-        self.patch.state = newstates[1]
-        self.patch.save()
-        self.assertEqual(PatchChangeNotification.objects.count(), 1)
-        notification = PatchChangeNotification.objects.all()[0]
-        self.assertEqual(notification.orig_state, oldstate)
-        self.assertTrue(notification.last_modified > orig_timestamp)
-
-    def testProjectNotificationsDisabled(self):
-        """Ensure we don't see notifications created when a project is
-           configured not to send them"""
-        self.project.send_notifications = False
-        self.project.save()
-
-        self.patch.save()
-        oldstate = self.patch.state
-        state = State.objects.exclude(pk = oldstate.pk)[0]
-
-        self.patch.state = state
-        self.patch.save()
-        self.assertEqual(PatchChangeNotification.objects.count(), 0)
-
-class PatchNotificationEmailTest(TestCase):
-
-    def setUp(self):
-        self.project = defaults.project
-        self.project.send_notifications = True
-        self.project.save()
-        self.submitter = defaults.patch_author_person
-        self.submitter.save()
-        self.patch = Patch(project = self.project, msgid = 'testpatch',
-                        name = 'testpatch', content = '',
-                        submitter = self.submitter)
-        self.patch.save()
-
-    def tearDown(self):
-        self.patch.delete()
-        self.submitter.delete()
-        self.project.delete()
-
-    def _expireNotifications(self, **kwargs):
-        timestamp = datetime.datetime.now() - \
-                    datetime.timedelta(minutes =
-                            settings.NOTIFICATION_DELAY_MINUTES + 1)
-
-        qs = PatchChangeNotification.objects.all()
-        if kwargs:
-            qs = qs.filter(**kwargs)
-
-        qs.update(last_modified = timestamp)
-
-    def testNoNotifications(self):
-        self.assertEquals(send_notifications(), [])
-
-    def testNoReadyNotifications(self):
-        """ We shouldn't see immediate notifications"""
-        PatchChangeNotification(patch = self.patch,
-                               orig_state = self.patch.state).save()
-
-        errors = send_notifications()
-        self.assertEquals(errors, [])
-        self.assertEquals(len(mail.outbox), 0)
-
-    def testNotifications(self):
-        PatchChangeNotification(patch = self.patch,
-                               orig_state = self.patch.state).save()
-        self._expireNotifications()
-
-        errors = send_notifications()
-        self.assertEquals(errors, [])
-        self.assertEquals(len(mail.outbox), 1)
-        msg = mail.outbox[0]
-        self.assertEquals(msg.to, [self.submitter.email])
-        self.assertTrue(self.patch.get_absolute_url() in msg.body)
-
-    def testNotificationEscaping(self):
-        self.patch.name = 'Patch name with " character'
-        self.patch.save()
-        PatchChangeNotification(patch = self.patch,
-                               orig_state = self.patch.state).save()
-        self._expireNotifications()
-
-        errors = send_notifications()
-        self.assertEquals(errors, [])
-        self.assertEquals(len(mail.outbox), 1)
-        msg = mail.outbox[0]
-        self.assertEquals(msg.to, [self.submitter.email])
-        self.assertFalse('&quot;' in msg.body)
-
-    def testNotificationOptout(self):
-        """ensure opt-out addresses don't get notifications"""
-        PatchChangeNotification(patch = self.patch,
-                               orig_state = self.patch.state).save()
-        self._expireNotifications()
-
-        EmailOptout(email = self.submitter.email).save()
-
-        errors = send_notifications()
-        self.assertEquals(errors, [])
-        self.assertEquals(len(mail.outbox), 0)
-
-    def testNotificationMerge(self):
-        patches = [self.patch,
-                   Patch(project = self.project, msgid = 'testpatch-2',
-                         name = 'testpatch 2', content = '',
-                         submitter = self.submitter)]
-
-        for patch in patches:
-            patch.save()
-            PatchChangeNotification(patch = patch,
-                                   orig_state = patch.state).save()
-
-        self.assertEquals(PatchChangeNotification.objects.count(), len(patches))
-        self._expireNotifications()
-        errors = send_notifications()
-        self.assertEquals(errors, [])
-        self.assertEquals(len(mail.outbox), 1)
-        msg = mail.outbox[0]
-        self.assertTrue(patches[0].get_absolute_url() in msg.body)
-        self.assertTrue(patches[1].get_absolute_url() in msg.body)
-
-    def testUnexpiredNotificationMerge(self):
-        """Test that when there are multiple pending notifications, with
-           at least one within the notification delay, that other notifications
-           are held"""
-        patches = [self.patch,
-                   Patch(project = self.project, msgid = 'testpatch-2',
-                         name = 'testpatch 2', content = '',
-                         submitter = self.submitter)]
-
-        for patch in patches:
-            patch.save()
-            PatchChangeNotification(patch = patch,
-                                   orig_state = patch.state).save()
-
-        self.assertEquals(PatchChangeNotification.objects.count(), len(patches))
-        self._expireNotifications()
-
-        # update one notification, to bring it out of the notification delay
-        patches[0].state = State.objects.exclude(pk = patches[0].state.pk)[0]
-        patches[0].save()
-
-        # the updated notification should prevent the other from being sent
-        errors = send_notifications()
-        self.assertEquals(errors, [])
-        self.assertEquals(len(mail.outbox), 0)
-
-        # expire the updated notification
-        self._expireNotifications()
-
-        errors = send_notifications()
-        self.assertEquals(errors, [])
-        self.assertEquals(len(mail.outbox), 1)
-        msg = mail.outbox[0]
-        self.assertTrue(patches[0].get_absolute_url() in msg.body)
-        self.assertTrue(patches[1].get_absolute_url() in msg.body)
diff --git a/apps/patchwork/tests/patchparser.py b/apps/patchwork/tests/patchparser.py
deleted file mode 100644 (file)
index 0496a69..0000000
+++ /dev/null
@@ -1,528 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
-#
-# This file is part of the Patchwork package.
-#
-# Patchwork is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# Patchwork is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with Patchwork; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-import os
-from email import message_from_string
-from django.test import TestCase
-from patchwork.models import Project, Person, Patch, Comment, State, \
-         get_default_initial_patch_state
-from patchwork.tests.utils import read_patch, read_mail, create_email, \
-         defaults, create_user
-
-try:
-    from email.mime.text import MIMEText
-except ImportError:
-    # Python 2.4 compatibility
-    from email.MIMEText import MIMEText
-
-class PatchTest(TestCase):
-    default_sender = defaults.sender
-    default_subject = defaults.subject
-    project = defaults.project
-
-from patchwork.bin.parsemail import find_content, find_author, find_project, \
-                                    parse_mail
-
-class InlinePatchTest(PatchTest):
-    patch_filename = '0001-add-line.patch'
-    test_comment = 'Test for attached patch'
-
-    def setUp(self):
-        self.orig_patch = read_patch(self.patch_filename)
-        email = create_email(self.test_comment + '\n' + self.orig_patch)
-        (self.patch, self.comment) = find_content(self.project, email)
-
-    def testPatchPresence(self):
-        self.assertTrue(self.patch is not None)
-
-    def testPatchContent(self):
-        self.assertEquals(self.patch.content, self.orig_patch)
-
-    def testCommentPresence(self):
-        self.assertTrue(self.comment is not None)
-
-    def testCommentContent(self):
-        self.assertEquals(self.comment.content, self.test_comment)
-
-
-class AttachmentPatchTest(InlinePatchTest):
-    patch_filename = '0001-add-line.patch'
-    test_comment = 'Test for attached patch'
-    content_subtype = 'x-patch'
-
-    def setUp(self):
-        self.orig_patch = read_patch(self.patch_filename)
-        email = create_email(self.test_comment, multipart = True)
-        attachment = MIMEText(self.orig_patch, _subtype = self.content_subtype)
-        email.attach(attachment)
-        (self.patch, self.comment) = find_content(self.project, email)
-
-class AttachmentXDiffPatchTest(AttachmentPatchTest):
-    content_subtype = 'x-diff'
-
-class UTF8InlinePatchTest(InlinePatchTest):
-    patch_filename = '0002-utf-8.patch'
-    patch_encoding = 'utf-8'
-
-    def setUp(self):
-        self.orig_patch = read_patch(self.patch_filename, self.patch_encoding)
-        email = create_email(self.test_comment + '\n' + self.orig_patch,
-                             content_encoding = self.patch_encoding)
-        (self.patch, self.comment) = find_content(self.project, email)
-
-class NoCharsetInlinePatchTest(InlinePatchTest):
-    """ Test mails with no content-type or content-encoding header """
-    patch_filename = '0001-add-line.patch'
-
-    def setUp(self):
-        self.orig_patch = read_patch(self.patch_filename)
-        email = create_email(self.test_comment + '\n' + self.orig_patch)
-        del email['Content-Type']
-        del email['Content-Transfer-Encoding']
-        (self.patch, self.comment) = find_content(self.project, email)
-
-class SignatureCommentTest(InlinePatchTest):
-    patch_filename = '0001-add-line.patch'
-    test_comment = 'Test comment\nmore comment'
-
-    def setUp(self):
-        self.orig_patch = read_patch(self.patch_filename)
-        email = create_email( \
-                self.test_comment + '\n' + \
-                '-- \nsig\n' + self.orig_patch)
-        (self.patch, self.comment) = find_content(self.project, email)
-
-
-class ListFooterTest(InlinePatchTest):
-    patch_filename = '0001-add-line.patch'
-    test_comment = 'Test comment\nmore comment'
-
-    def setUp(self):
-        self.orig_patch = read_patch(self.patch_filename)
-        email = create_email( \
-                self.test_comment + '\n' + \
-                '_______________________________________________\n' + \
-                'Linuxppc-dev mailing list\n' + \
-                self.orig_patch)
-        (self.patch, self.comment) = find_content(self.project, email)
-
-
-class DiffWordInCommentTest(InlinePatchTest):
-    test_comment = 'Lines can start with words beginning in "diff"\n' + \
-                   'difficult\nDifferent'
-
-
-class UpdateCommentTest(InlinePatchTest):
-    """ Test for '---\nUpdate: v2' style comments to patches. """
-    patch_filename = '0001-add-line.patch'
-    test_comment = 'Test comment\nmore comment\n---\nUpdate: test update'
-
-class UpdateSigCommentTest(SignatureCommentTest):
-    """ Test for '---\nUpdate: v2' style comments to patches, with a sig """
-    patch_filename = '0001-add-line.patch'
-    test_comment = 'Test comment\nmore comment\n---\nUpdate: test update'
-
-class SenderEncodingTest(TestCase):
-    sender_name = u'example user'
-    sender_email = 'user@example.com'
-    from_header = 'example user <user@example.com>'
-
-    def setUp(self):
-        mail = 'From: %s\n' % self.from_header + \
-               'Subject: test\n\n' + \
-               'test'
-        self.email = message_from_string(mail)
-        (self.person, new) = find_author(self.email)
-        self.person.save()
-
-    def tearDown(self):
-        self.person.delete()
-
-    def testName(self):
-        self.assertEquals(self.person.name, self.sender_name)
-
-    def testEmail(self):
-        self.assertEquals(self.person.email, self.sender_email)
-
-    def testDBQueryName(self):
-        db_person = Person.objects.get(name = self.sender_name)
-        self.assertEquals(self.person, db_person)
-
-    def testDBQueryEmail(self):
-        db_person = Person.objects.get(email = self.sender_email)
-        self.assertEquals(self.person, db_person)
-
-
-class SenderUTF8QPEncodingTest(SenderEncodingTest):
-    sender_name = u'\xe9xample user'
-    from_header = '=?utf-8?q?=C3=A9xample=20user?= <user@example.com>'
-
-class SenderUTF8QPSplitEncodingTest(SenderEncodingTest):
-    sender_name = u'\xe9xample user'
-    from_header = '=?utf-8?q?=C3=A9xample?= user <user@example.com>'
-
-class SenderUTF8B64EncodingTest(SenderUTF8QPEncodingTest):
-    from_header = '=?utf-8?B?w6l4YW1wbGUgdXNlcg==?= <user@example.com>'
-
-class SubjectEncodingTest(PatchTest):
-    sender = 'example user <user@example.com>'
-    subject = 'test subject'
-    subject_header = 'test subject'
-
-    def setUp(self):
-        mail = 'From: %s\n' % self.sender + \
-               'Subject: %s\n\n' % self.subject_header + \
-               'test\n\n' + defaults.patch
-        self.projects = defaults.project
-        self.email = message_from_string(mail)
-
-    def testSubjectEncoding(self):
-        (patch, comment) = find_content(self.project, self.email)
-        self.assertEquals(patch.name, self.subject)
-
-class SubjectUTF8QPEncodingTest(SubjectEncodingTest):
-    subject = u'test s\xfcbject'
-    subject_header = '=?utf-8?q?test=20s=c3=bcbject?='
-
-class SubjectUTF8QPMultipleEncodingTest(SubjectEncodingTest):
-    subject = u'test s\xfcbject'
-    subject_header = 'test =?utf-8?q?s=c3=bcbject?='
-
-class SenderCorrelationTest(TestCase):
-    existing_sender = 'Existing Sender <existing@example.com>'
-    non_existing_sender = 'Non-existing Sender <nonexisting@example.com>'
-
-    def mail(self, sender):
-        return message_from_string('From: %s\nSubject: Test\n\ntest\n' % sender)
-
-    def setUp(self):
-        self.existing_sender_mail = self.mail(self.existing_sender)
-        self.non_existing_sender_mail = self.mail(self.non_existing_sender)
-        (self.person, new) = find_author(self.existing_sender_mail)
-        self.person.save()
-
-    def testExisingSender(self):
-        (person, new) = find_author(self.existing_sender_mail)
-        self.assertEqual(new, False)
-        self.assertEqual(person.id, self.person.id)
-
-    def testNonExisingSender(self):
-        (person, new) = find_author(self.non_existing_sender_mail)
-        self.assertEqual(new, True)
-        self.assertEqual(person.id, None)
-
-    def testExistingDifferentFormat(self):
-        mail = self.mail('existing@example.com')
-        (person, new) = find_author(mail)
-        self.assertEqual(new, False)
-        self.assertEqual(person.id, self.person.id)
-
-    def testExistingDifferentCase(self):
-        mail = self.mail(self.existing_sender.upper())
-        (person, new) = find_author(mail)
-        self.assertEqual(new, False)
-        self.assertEqual(person.id, self.person.id)
-
-    def tearDown(self):
-        self.person.delete()
-
-class MultipleProjectPatchTest(TestCase):
-    """ Test that patches sent to multiple patchwork projects are
-        handled correctly """
-
-    test_comment = 'Test Comment'
-    patch_filename = '0001-add-line.patch'
-    msgid = '<1@example.com>'
-
-    def setUp(self):
-        self.p1 = Project(linkname = 'test-project-1', name = 'Project 1',
-                listid = '1.example.com', listemail='1@example.com')
-        self.p2 = Project(linkname = 'test-project-2', name = 'Project 2',
-                listid = '2.example.com', listemail='2@example.com')
-
-        self.p1.save()
-        self.p2.save()
-
-        patch = read_patch(self.patch_filename)
-        email = create_email(self.test_comment + '\n' + patch)
-        email['Message-Id'] = self.msgid
-
-        del email['List-ID']
-        email['List-ID'] = '<' + self.p1.listid + '>'
-        parse_mail(email)
-
-        del email['List-ID']
-        email['List-ID'] = '<' + self.p2.listid + '>'
-        parse_mail(email)
-
-    def testParsedProjects(self):
-        self.assertEquals(Patch.objects.filter(project = self.p1).count(), 1)
-        self.assertEquals(Patch.objects.filter(project = self.p2).count(), 1)
-
-    def tearDown(self):
-        self.p1.delete()
-        self.p2.delete()
-
-
-class MultipleProjectPatchCommentTest(MultipleProjectPatchTest):
-    """ Test that followups to multiple-project patches end up on the
-        correct patch """
-
-    comment_msgid = '<2@example.com>'
-    comment_content = 'test comment'
-
-    def setUp(self):
-        super(MultipleProjectPatchCommentTest, self).setUp()
-
-        for project in [self.p1, self.p2]:
-            email = MIMEText(self.comment_content)
-            email['From'] = defaults.sender
-            email['Subject'] = defaults.subject
-            email['Message-Id'] = self.comment_msgid
-            email['List-ID'] = '<' + project.listid + '>'
-            email['In-Reply-To'] = self.msgid
-            parse_mail(email)
-
-    def testParsedComment(self):
-        for project in [self.p1, self.p2]:
-            patch = Patch.objects.filter(project = project)[0]
-            # we should see two comments now - the original mail with the patch,
-            # and the one we parsed in setUp()
-            self.assertEquals(Comment.objects.filter(patch = patch).count(), 2)
-
-class ListIdHeaderTest(TestCase):
-    """ Test that we parse List-Id headers from mails correctly """
-    def setUp(self):
-        self.project = Project(linkname = 'test-project-1', name = 'Project 1',
-                listid = '1.example.com', listemail='1@example.com')
-        self.project.save()
-
-    def testNoListId(self):
-        email = MIMEText('')
-        project = find_project(email)
-        self.assertEquals(project, None)
-
-    def testBlankListId(self):
-        email = MIMEText('')
-        email['List-Id'] = ''
-        project = find_project(email)
-        self.assertEquals(project, None)
-
-    def testWhitespaceListId(self):
-        email = MIMEText('')
-        email['List-Id'] = ' '
-        project = find_project(email)
-        self.assertEquals(project, None)
-
-    def testSubstringListId(self):
-        email = MIMEText('')
-        email['List-Id'] = 'example.com'
-        project = find_project(email)
-        self.assertEquals(project, None)
-
-    def testShortListId(self):
-        """ Some mailing lists have List-Id headers in short formats, where it
-            is only the list ID itself (without enclosing angle-brackets). """
-        email = MIMEText('')
-        email['List-Id'] = self.project.listid
-        project = find_project(email)
-        self.assertEquals(project, self.project)
-
-    def testLongListId(self):
-        email = MIMEText('')
-        email['List-Id'] = 'Test text <%s>' % self.project.listid
-        project = find_project(email)
-        self.assertEquals(project, self.project)
-
-    def tearDown(self):
-        self.project.delete()
-
-class MBoxPatchTest(PatchTest):
-    def setUp(self):
-        self.mail = read_mail(self.mail_file, project = self.project)
-
-class GitPullTest(MBoxPatchTest):
-    mail_file = '0001-git-pull-request.mbox'
-
-    def testGitPullRequest(self):
-        (patch, comment) = find_content(self.project, self.mail)
-        self.assertTrue(patch is not None)
-        self.assertTrue(patch.pull_url is not None)
-        self.assertTrue(patch.content is None)
-        self.assertTrue(comment is not None)
-
-class GitPullWrappedTest(GitPullTest):
-    mail_file = '0002-git-pull-request-wrapped.mbox'
-
-class GitPullWithDiffTest(MBoxPatchTest):
-    mail_file = '0003-git-pull-request-with-diff.mbox'
-
-    def testGitPullWithDiff(self):
-        (patch, comment) = find_content(self.project, self.mail)
-        self.assertTrue(patch is not None)
-        self.assertEqual('git://git.kernel.org/pub/scm/linux/kernel/git/tip/' +
-             'linux-2.6-tip.git x86-fixes-for-linus', patch.pull_url)
-        self.assertTrue(
-            patch.content.startswith('diff --git a/arch/x86/include/asm/smp.h'),
-            patch.content)
-        self.assertTrue(comment is not None)
-
-class GitPullGitSSHUrlTest(GitPullTest):
-    mail_file = '0004-git-pull-request-git+ssh.mbox'
-
-class GitPullSSHUrlTest(GitPullTest):
-    mail_file = '0005-git-pull-request-ssh.mbox'
-
-class GitPullHTTPUrlTest(GitPullTest):
-    mail_file = '0006-git-pull-request-http.mbox'
-
-class GitRenameOnlyTest(MBoxPatchTest):
-    mail_file = '0008-git-rename.mbox'
-
-    def testGitRename(self):
-        (patch, comment) = find_content(self.project, self.mail)
-        self.assertTrue(patch is not None)
-        self.assertTrue(comment is not None)
-        self.assertEqual(patch.content.count("\nrename from "), 2)
-        self.assertEqual(patch.content.count("\nrename to "), 2)
-
-class GitRenameWithDiffTest(MBoxPatchTest):
-    mail_file = '0009-git-rename-with-diff.mbox'
-
-    def testGitRename(self):
-        (patch, comment) = find_content(self.project, self.mail)
-        self.assertTrue(patch is not None)
-        self.assertTrue(comment is not None)
-        self.assertEqual(patch.content.count("\nrename from "), 2)
-        self.assertEqual(patch.content.count("\nrename to "), 2)
-        self.assertEqual(patch.content.count('\n-a\n+b'), 1)
-
-class CVSFormatPatchTest(MBoxPatchTest):
-    mail_file = '0007-cvs-format-diff.mbox'
-
-    def testPatch(self):
-        (patch, comment) = find_content(self.project, self.mail)
-        self.assertTrue(patch is not None)
-        self.assertTrue(comment is not None)
-        self.assertTrue(patch.content.startswith('Index'))
-
-class DelegateRequestTest(TestCase):
-    patch_filename = '0001-add-line.patch'
-    msgid = '<1@example.com>'
-    invalid_delegate_email = "nobody"
-
-    def setUp(self):
-        self.patch = read_patch(self.patch_filename)
-        self.user = create_user()
-        self.p1 = Project(linkname = 'test-project-1', name = 'Project 1',
-                listid = '1.example.com', listemail='1@example.com')
-        self.p1.save()
-
-    def get_email(self):
-        email = create_email(self.patch)
-        del email['List-ID']
-        email['List-ID'] = '<' + self.p1.listid + '>'
-        email['Message-Id'] = self.msgid
-        return email
-
-    def _assertDelegate(self, delegate):
-        query = Patch.objects.filter(project=self.p1)
-        self.assertEquals(query.count(), 1)
-        self.assertEquals(query[0].delegate, delegate)
-
-    def testDelegate(self):
-        email = self.get_email()
-        email['X-Patchwork-Delegate'] = self.user.email
-        parse_mail(email)
-        self._assertDelegate(self.user)
-
-    def testNoDelegate(self):
-        email = self.get_email()
-        parse_mail(email)
-        self._assertDelegate(None)
-
-    def testInvalidDelegateFallsBackToNoDelegate(self):
-        email = self.get_email()
-        email['X-Patchwork-Delegate'] = self.invalid_delegate_email
-        parse_mail(email)
-        self._assertDelegate(None)
-
-    def tearDown(self):
-        self.p1.delete()
-        self.user.delete()
-
-class InitialPatchStateTest(TestCase):
-    patch_filename = '0001-add-line.patch'
-    msgid = '<1@example.com>'
-    invalid_state_name = "Nonexistent Test State"
-
-    def setUp(self):
-        self.patch = read_patch(self.patch_filename)
-        self.user = create_user()
-        self.p1 = Project(linkname = 'test-project-1', name = 'Project 1',
-                listid = '1.example.com', listemail='1@example.com')
-        self.p1.save()
-        self.default_state = get_default_initial_patch_state()
-        self.nondefault_state = State.objects.get(name="Accepted")
-
-    def get_email(self):
-        email = create_email(self.patch)
-        del email['List-ID']
-        email['List-ID'] = '<' + self.p1.listid + '>'
-        email['Message-Id'] = self.msgid
-        return email
-
-    def _assertState(self, state):
-        query = Patch.objects.filter(project=self.p1)
-        self.assertEquals(query.count(), 1)
-        self.assertEquals(query[0].state, state)
-
-    def testNonDefaultStateIsActuallyNotTheDefaultState(self):
-        self.assertNotEqual(self.default_state, self.nondefault_state)
-
-    def testExplicitNonDefaultStateRequest(self):
-        email = self.get_email()
-        email['X-Patchwork-State'] = self.nondefault_state.name
-        parse_mail(email)
-        self._assertState(self.nondefault_state)
-
-    def testExplicitDefaultStateRequest(self):
-        email = self.get_email()
-        email['X-Patchwork-State'] = self.default_state.name
-        parse_mail(email)
-        self._assertState(self.default_state)
-
-    def testImplicitDefaultStateRequest(self):
-        email = self.get_email()
-        parse_mail(email)
-        self._assertState(self.default_state)
-
-    def testInvalidTestStateDoesNotExist(self):
-        with self.assertRaises(State.DoesNotExist):
-            State.objects.get(name=self.invalid_state_name)
-
-    def testInvalidStateRequestFallsBackToDefaultState(self):
-        email = self.get_email()
-        email['X-Patchwork-State'] = self.invalid_state_name
-        parse_mail(email)
-        self._assertState(self.default_state)
-
-    def tearDown(self):
-        self.p1.delete()
-        self.user.delete()
diff --git a/apps/patchwork/tests/person.py b/apps/patchwork/tests/person.py
deleted file mode 100644 (file)
index d948096..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2013 Jeremy Kerr <jk@ozlabs.org>
-#
-# This file is part of the Patchwork package.
-#
-# Patchwork is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# Patchwork is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with Patchwork; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-import unittest
-from django.test import TestCase
-from django.test.client import Client
-from patchwork.models import EmailConfirmation, Person, Bundle
-import json
-
-class SubmitterCompletionTest(TestCase):
-    def setUp(self):
-        self.people = [
-            Person(name = "Test Name", email = "test1@example.com"),
-            Person(email = "test2@example.com"),
-        ]
-        map(lambda p: p.save(), self.people)
-
-    def testNameComplete(self):
-        response = self.client.get('/submitter/', {'q': 'name'})
-        self.assertEquals(response.status_code, 200)
-        data = json.loads(response.content)
-        self.assertEquals(len(data), 1)
-        self.assertEquals(data[0]['fields']['name'], 'Test Name')
-
-    def testEmailComplete(self):
-        response = self.client.get('/submitter/', {'q': 'test2'})
-        self.assertEquals(response.status_code, 200)
-        data = json.loads(response.content)
-        self.assertEquals(len(data), 1)
-        self.assertEquals(data[0]['fields']['email'], 'test2@example.com')
-
-    def testCompleteLimit(self):
-        for i in range(3,10):
-            person = Person(email = 'test%d@example.com' % i)
-            person.save()
-        response = self.client.get('/submitter/', {'q': 'test', 'l': 5})
-        self.assertEquals(response.status_code, 200)
-        data = json.loads(response.content)
-        self.assertEquals(len(data), 5)
diff --git a/apps/patchwork/tests/registration.py b/apps/patchwork/tests/registration.py
deleted file mode 100644 (file)
index 845b60b..0000000
+++ /dev/null
@@ -1,210 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2010 Jeremy Kerr <jk@ozlabs.org>
-#
-# This file is part of the Patchwork package.
-#
-# Patchwork is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# Patchwork is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with Patchwork; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-import unittest
-from django.test import TestCase
-from django.test.client import Client
-from django.core import mail
-from django.core.urlresolvers import reverse
-from django.contrib.auth.models import User
-from patchwork.models import EmailConfirmation, Person
-from patchwork.tests.utils import create_user
-
-def _confirmation_url(conf):
-    return reverse('patchwork.views.confirm', kwargs = {'key': conf.key})
-
-class TestUser(object):
-    firstname = 'Test'
-    lastname = 'User'
-    username = 'testuser'
-    email = 'test@example.com'
-    password = 'foobar'
-
-class RegistrationTest(TestCase):
-    def setUp(self):
-        self.user = TestUser()
-        self.client = Client()
-        self.default_data = {'username': self.user.username,
-                             'first_name': self.user.firstname,
-                             'last_name': self.user.lastname,
-                             'email': self.user.email,
-                             'password': self.user.password}
-        self.required_error = 'This field is required.'
-        self.invalid_error = 'Enter a valid value.'
-
-    def testRegistrationForm(self):
-        response = self.client.get('/register/')
-        self.assertEquals(response.status_code, 200)
-        self.assertTemplateUsed(response, 'patchwork/registration_form.html')
-
-    def testBlankFields(self):
-        for field in ['username', 'email', 'password']:
-            data = self.default_data.copy()
-            del data[field]
-            response = self.client.post('/register/', data)
-            self.assertEquals(response.status_code, 200)
-            self.assertFormError(response, 'form', field, self.required_error)
-
-    def testInvalidUsername(self):
-        data = self.default_data.copy()
-        data['username'] = 'invalid user'
-        response = self.client.post('/register/', data)
-        self.assertEquals(response.status_code, 200)
-        self.assertFormError(response, 'form', 'username', self.invalid_error)
-
-    def testExistingUsername(self):
-        user = create_user()
-        data = self.default_data.copy()
-        data['username'] = user.username
-        response = self.client.post('/register/', data)
-        self.assertEquals(response.status_code, 200)
-        self.assertFormError(response, 'form', 'username',
-                'This username is already taken. Please choose another.')
-
-    def testExistingEmail(self):
-        user = create_user()
-        data = self.default_data.copy()
-        data['email'] = user.email
-        response = self.client.post('/register/', data)
-        self.assertEquals(response.status_code, 200)
-        self.assertFormError(response, 'form', 'email',
-                'This email address is already in use ' + \
-                'for the account "%s".\n' % user.username)
-
-    def testValidRegistration(self):
-        response = self.client.post('/register/', self.default_data)
-        self.assertEquals(response.status_code, 200)
-        self.assertContains(response, 'confirmation email has been sent')
-
-        # check for presence of an inactive user object
-        users = User.objects.filter(username = self.user.username)
-        self.assertEquals(users.count(), 1)
-        user = users[0]
-        self.assertEquals(user.username, self.user.username)
-        self.assertEquals(user.email, self.user.email)
-        self.assertEquals(user.is_active, False)
-
-        # check for confirmation object
-        confs = EmailConfirmation.objects.filter(user = user,
-                                                 type = 'registration')
-        self.assertEquals(len(confs), 1)
-        conf = confs[0]
-        self.assertEquals(conf.email, self.user.email)
-
-        # check for a sent mail
-        self.assertEquals(len(mail.outbox), 1)
-        msg = mail.outbox[0]
-        self.assertEquals(msg.subject, 'Patchwork account confirmation')
-        self.assertTrue(self.user.email in msg.to)
-        self.assertTrue(_confirmation_url(conf) in msg.body)
-
-        # ...and that the URL is valid
-        response = self.client.get(_confirmation_url(conf))
-        self.assertEquals(response.status_code, 200)
-
-class RegistrationConfirmationTest(TestCase):
-
-    def setUp(self):
-        self.user = TestUser()
-        self.default_data = {'username': self.user.username,
-                             'first_name': self.user.firstname,
-                             'last_name': self.user.lastname,
-                             'email': self.user.email,
-                             'password': self.user.password}
-
-    def testRegistrationConfirmation(self):
-        self.assertEqual(EmailConfirmation.objects.count(), 0)
-        response = self.client.post('/register/', self.default_data)
-        self.assertEquals(response.status_code, 200)
-        self.assertContains(response, 'confirmation email has been sent')
-
-        self.assertEqual(EmailConfirmation.objects.count(), 1)
-        conf = EmailConfirmation.objects.filter()[0]
-        self.assertFalse(conf.user.is_active)
-        self.assertTrue(conf.active)
-
-        response = self.client.get(_confirmation_url(conf))
-        self.assertEquals(response.status_code, 200)
-        self.assertTemplateUsed(response, 'patchwork/registration-confirm.html')
-
-        conf = EmailConfirmation.objects.get(pk = conf.pk)
-        self.assertTrue(conf.user.is_active)
-        self.assertFalse(conf.active)
-
-    def testRegistrationNewPersonSetup(self):
-        """ Check that the person object created after registration has the
-            correct details """
-
-        # register
-        self.assertEqual(EmailConfirmation.objects.count(), 0)
-        response = self.client.post('/register/', self.default_data)
-        self.assertEquals(response.status_code, 200)
-        self.assertFalse(Person.objects.exists())
-
-        # confirm
-        conf = EmailConfirmation.objects.filter()[0]
-        response = self.client.get(_confirmation_url(conf))
-        self.assertEquals(response.status_code, 200)
-
-        qs = Person.objects.filter(email = self.user.email)
-        self.assertTrue(qs.exists())
-        person = Person.objects.get(email = self.user.email)
-
-        self.assertEquals(person.name,
-                    self.user.firstname + ' ' + self.user.lastname)
-
-    def testRegistrationExistingPersonSetup(self):
-        """ Check that the person object created after registration has the
-            correct details """
-
-        fullname = self.user.firstname + ' '  + self.user.lastname
-        person = Person(name = fullname, email = self.user.email)
-        person.save()
-
-        # register
-        self.assertEqual(EmailConfirmation.objects.count(), 0)
-        response = self.client.post('/register/', self.default_data)
-        self.assertEquals(response.status_code, 200)
-
-        # confirm
-        conf = EmailConfirmation.objects.filter()[0]
-        response = self.client.get(_confirmation_url(conf))
-        self.assertEquals(response.status_code, 200)
-
-        person = Person.objects.get(email = self.user.email)
-
-        self.assertEquals(person.name, fullname)
-
-    def testRegistrationExistingPersonUnmodified(self):
-        """ Check that an unconfirmed registration can't modify an existing
-            Person object"""
-
-        fullname = self.user.firstname + ' '  + self.user.lastname
-        person = Person(name = fullname, email = self.user.email)
-        person.save()
-
-        # register
-        data = self.default_data.copy()
-        data['first_name'] = 'invalid'
-        data['last_name'] = 'invalid'
-        self.assertEquals(data['email'], person.email)
-        response = self.client.post('/register/', data)
-        self.assertEquals(response.status_code, 200)
-
-        self.assertEquals(Person.objects.get(pk = person.pk).name, fullname)
diff --git a/apps/patchwork/tests/test_bundles.py b/apps/patchwork/tests/test_bundles.py
new file mode 100644 (file)
index 0000000..5e8b95b
--- /dev/null
@@ -0,0 +1,646 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2009 Jeremy Kerr <jk@ozlabs.org>
+#
+# This file is part of the Patchwork package.
+#
+# Patchwork is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# Patchwork is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Patchwork; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+import unittest
+import datetime
+from django.test import TestCase
+from django.test.client import Client
+from django.utils.http import urlencode
+from django.conf import settings
+from patchwork.models import Patch, Bundle, BundlePatch, Person
+from patchwork.tests.utils import defaults, create_user, find_in_context
+
+def bundle_url(bundle):
+    return '/bundle/%s/%s/' % (bundle.owner.username, bundle.name)
+
+class BundleListTest(TestCase):
+    def setUp(self):
+        self.user = create_user()
+        self.client.login(username = self.user.username,
+                password = self.user.username)
+
+    def testNoBundles(self):
+        response = self.client.get('/user/bundles/')
+        self.failUnlessEqual(response.status_code, 200)
+        self.failUnlessEqual(
+                len(find_in_context(response.context, 'bundles')), 0)
+
+    def testSingleBundle(self):
+        defaults.project.save()
+        bundle = Bundle(owner = self.user, project = defaults.project)
+        bundle.save()
+        response = self.client.get('/user/bundles/')
+        self.failUnlessEqual(response.status_code, 200)
+        self.failUnlessEqual(
+                len(find_in_context(response.context, 'bundles')), 1)
+
+    def tearDown(self):
+        self.user.delete()
+
+class BundleTestBase(TestCase):
+    def setUp(self, patch_count=3):
+        patch_names = ['testpatch%d' % (i) for i in range(1, patch_count+1)]
+        self.user = create_user()
+        self.client.login(username = self.user.username,
+                password = self.user.username)
+        defaults.project.save()
+        self.bundle = Bundle(owner = self.user, project = defaults.project,
+                name = 'testbundle')
+        self.bundle.save()
+        self.patches = []
+
+        for patch_name in patch_names:
+            patch = Patch(project = defaults.project,
+                               msgid = patch_name, name = patch_name,
+                               submitter = Person.objects.get(user = self.user),
+                               content = '')
+            patch.save()
+            self.patches.append(patch)
+
+    def tearDown(self):
+        for patch in self.patches:
+            patch.delete()
+        self.bundle.delete()
+        self.user.delete()
+
+class BundleViewTest(BundleTestBase):
+
+    def testEmptyBundle(self):
+        response = self.client.get(bundle_url(self.bundle))
+        self.failUnlessEqual(response.status_code, 200)
+        page = find_in_context(response.context, 'page')
+        self.failUnlessEqual(len(page.object_list), 0)
+
+    def testNonEmptyBundle(self):
+        self.bundle.append_patch(self.patches[0])
+
+        response = self.client.get(bundle_url(self.bundle))
+        self.failUnlessEqual(response.status_code, 200)
+        page = find_in_context(response.context, 'page')
+        self.failUnlessEqual(len(page.object_list), 1)
+
+    def testBundleOrder(self):
+        for patch in self.patches:
+            self.bundle.append_patch(patch)
+
+        response = self.client.get(bundle_url(self.bundle))
+
+        pos = 0
+        for patch in self.patches:
+            next_pos = response.content.find(patch.name)
+            # ensure that this patch is after the previous
+            self.failUnless(next_pos > pos)
+            pos = next_pos
+
+        # reorder and recheck
+        i = 0
+        for patch in self.patches.__reversed__():
+            bundlepatch = BundlePatch.objects.get(bundle = self.bundle,
+                    patch = patch)
+            bundlepatch.order = i
+            bundlepatch.save()
+            i += 1
+
+        response = self.client.get(bundle_url(self.bundle))
+        pos = len(response.content)
+        for patch in self.patches:
+            next_pos = response.content.find(patch.name)
+            # ensure that this patch is now *before* the previous
+            self.failUnless(next_pos < pos)
+            pos = next_pos
+
+class BundleUpdateTest(BundleTestBase):
+
+    def setUp(self):
+        super(BundleUpdateTest, self).setUp()
+        self.newname = 'newbundlename'
+
+    def checkPatchformErrors(self, response):
+        formname = 'patchform'
+        if not formname in response.context:
+            return
+        form = response.context[formname]
+        if not form:
+            return
+        self.assertEquals(form.errors, {})
+
+    def publicString(self, public):
+        if public:
+            return 'on'
+        return ''
+
+    def testNoAction(self):
+        data = {
+            'form': 'bundle',
+            'name': self.newname,
+            'public': self.publicString(not self.bundle.public)
+        }
+        response = self.client.post(bundle_url(self.bundle), data)
+        self.assertEqual(response.status_code, 200)
+
+        bundle = Bundle.objects.get(pk = self.bundle.pk)
+        self.assertEqual(bundle.name, self.bundle.name)
+        self.assertEqual(bundle.public, self.bundle.public)
+
+    def testUpdateName(self):
+        newname = 'newbundlename'
+        data = {
+            'form': 'bundle',
+            'action': 'update',
+            'name': newname,
+            'public': self.publicString(self.bundle.public)
+        }
+        response = self.client.post(bundle_url(self.bundle), data)
+        bundle = Bundle.objects.get(pk = self.bundle.pk)
+        self.assertRedirects(response, bundle_url(bundle))
+        self.assertEqual(bundle.name, newname)
+        self.assertEqual(bundle.public, self.bundle.public)
+
+    def testUpdatePublic(self):
+        newname = 'newbundlename'
+        data = {
+            'form': 'bundle',
+            'action': 'update',
+            'name': self.bundle.name,
+            'public': self.publicString(not self.bundle.public)
+        }
+        response = self.client.post(bundle_url(self.bundle), data)
+        self.assertEqual(response.status_code, 200)
+        bundle = Bundle.objects.get(pk = self.bundle.pk)
+        self.assertEqual(bundle.name, self.bundle.name)
+        self.assertEqual(bundle.public, not self.bundle.public)
+
+        # check other forms for errors
+        self.checkPatchformErrors(response)
+
+class BundleMaintainerUpdateTest(BundleUpdateTest):
+
+    def setUp(self):
+        super(BundleMaintainerUpdateTest, self).setUp()
+        profile = self.user.get_profile()
+        profile.maintainer_projects.add(defaults.project)
+        profile.save()
+
+class BundlePublicViewTest(BundleTestBase):
+
+    def setUp(self):
+        super(BundlePublicViewTest, self).setUp()
+        self.client.logout()
+        self.bundle.append_patch(self.patches[0])
+        self.url = bundle_url(self.bundle)
+
+    def testPublicBundle(self):
+        self.bundle.public = True
+        self.bundle.save()
+        response = self.client.get(self.url)
+        self.assertEqual(response.status_code, 200)
+        self.assertContains(response, self.patches[0].name)
+
+    def testPrivateBundle(self):
+        self.bundle.public = False
+        self.bundle.save()
+        response = self.client.get(self.url)
+        self.assertEqual(response.status_code, 404)
+
+class BundlePublicViewMboxTest(BundlePublicViewTest):
+    def setUp(self):
+        super(BundlePublicViewMboxTest, self).setUp()
+        self.url = bundle_url(self.bundle) + "mbox/"
+
+class BundlePublicModifyTest(BundleTestBase):
+    """Ensure that non-owners can't modify bundles"""
+
+    def setUp(self):
+        super(BundlePublicModifyTest, self).setUp()
+        self.bundle.public = True
+        self.bundle.save()
+        self.other_user = create_user()
+
+    def testBundleFormPresence(self):
+        """Check for presence of the modify form on the bundle"""
+        self.client.login(username = self.other_user.username,
+                password = self.other_user.username)
+        response = self.client.get(bundle_url(self.bundle))
+        self.assertNotContains(response, 'name="form" value="bundle"')
+        self.assertNotContains(response, 'Change order')
+
+    def testBundleFormSubmission(self):
+        oldname = 'oldbundlename'
+        newname = 'newbundlename'
+        data = {
+            'form': 'bundle',
+            'action': 'update',
+            'name': newname,
+        }
+        self.bundle.name = oldname
+        self.bundle.save()
+
+        # first, check that we can modify with the owner
+        self.client.login(username = self.user.username,
+                password = self.user.username)
+        response = self.client.post(bundle_url(self.bundle), data)
+        self.bundle = Bundle.objects.get(pk = self.bundle.pk)
+        self.assertEqual(self.bundle.name, newname)
+
+        # reset bundle name
+        self.bundle.name = oldname
+        self.bundle.save()
+
+        # log in with a different user, and check that we can no longer modify
+        self.client.login(username = self.other_user.username,
+                password = self.other_user.username)
+        response = self.client.post(bundle_url(self.bundle), data)
+        self.bundle = Bundle.objects.get(pk = self.bundle.pk)
+        self.assertNotEqual(self.bundle.name, newname)
+
+class BundleCreateFromListTest(BundleTestBase):
+    def testCreateEmptyBundle(self):
+        newbundlename = 'testbundle-new'
+        params = {'form': 'patchlistform',
+                  'bundle_name': newbundlename,
+                  'action': 'Create',
+                  'project': defaults.project.id}
+
+        response = self.client.post(
+                '/project/%s/list/' % defaults.project.linkname,
+                params)
+
+        self.assertContains(response, 'Bundle %s created' % newbundlename)
+
+    def testCreateNonEmptyBundle(self):
+        newbundlename = 'testbundle-new'
+        patch = self.patches[0]
+
+        params = {'form': 'patchlistform',
+                  'bundle_name': newbundlename,
+                  'action': 'Create',
+                  'project': defaults.project.id,
+                  'patch_id:%d' % patch.id: 'checked'}
+
+        response = self.client.post(
+                '/project/%s/list/' % defaults.project.linkname,
+                params)
+
+        self.assertContains(response, 'Bundle %s created' % newbundlename)
+        self.assertContains(response, 'added to bundle %s' % newbundlename,
+            count = 1)
+
+        bundle = Bundle.objects.get(name = newbundlename)
+        self.failUnlessEqual(bundle.patches.count(), 1)
+        self.failUnlessEqual(bundle.patches.all()[0], patch)
+
+    def testCreateNonEmptyBundleEmptyName(self):
+        newbundlename = 'testbundle-new'
+        patch = self.patches[0]
+
+        n_bundles = Bundle.objects.count()
+
+        params = {'form': 'patchlistform',
+                  'bundle_name': '',
+                  'action': 'Create',
+                  'project': defaults.project.id,
+                  'patch_id:%d' % patch.id: 'checked'}
+
+        response = self.client.post(
+                '/project/%s/list/' % defaults.project.linkname,
+                params)
+
+        self.assertContains(response, 'No bundle name was specified',
+                status_code = 200)
+
+        # test that no new bundles are present
+        self.failUnlessEqual(n_bundles, Bundle.objects.count())
+
+    def testCreateDuplicateName(self):
+        newbundlename = 'testbundle-dup'
+        patch = self.patches[0]
+
+        params = {'form': 'patchlistform',
+                  'bundle_name': newbundlename,
+                  'action': 'Create',
+                  'project': defaults.project.id,
+                  'patch_id:%d' % patch.id: 'checked'}
+
+        response = self.client.post(
+                '/project/%s/list/' % defaults.project.linkname,
+                params)
+
+        n_bundles = Bundle.objects.count()
+        self.assertContains(response, 'Bundle %s created' % newbundlename)
+        self.assertContains(response, 'added to bundle %s' % newbundlename,
+            count = 1)
+
+        bundle = Bundle.objects.get(name = newbundlename)
+        self.failUnlessEqual(bundle.patches.count(), 1)
+        self.failUnlessEqual(bundle.patches.all()[0], patch)
+
+        response = self.client.post(
+                '/project/%s/list/' % defaults.project.linkname,
+                params)
+
+        self.assertNotContains(response, 'Bundle %s created' % newbundlename)
+        self.assertContains(response, 'You already have a bundle called')
+        self.assertEqual(Bundle.objects.count(), n_bundles)
+        self.assertEqual(bundle.patches.count(), 1)
+
+class BundleCreateFromPatchTest(BundleTestBase):
+    def testCreateNonEmptyBundle(self):
+        newbundlename = 'testbundle-new'
+        patch = self.patches[0]
+
+        params = {'name': newbundlename,
+                  'action': 'createbundle'}
+
+        response = self.client.post('/patch/%d/' % patch.id, params)
+
+        self.assertContains(response,
+                'Bundle %s created' % newbundlename)
+
+        bundle = Bundle.objects.get(name = newbundlename)
+        self.failUnlessEqual(bundle.patches.count(), 1)
+        self.failUnlessEqual(bundle.patches.all()[0], patch)
+
+    def testCreateWithExistingName(self):
+        newbundlename = self.bundle.name
+        patch = self.patches[0]
+
+        params = {'name': newbundlename,
+                  'action': 'createbundle'}
+
+        response = self.client.post('/patch/%d/' % patch.id, params)
+
+        self.assertContains(response,
+                'A bundle called %s already exists' % newbundlename)
+
+        count = Bundle.objects.count()
+        self.failUnlessEqual(Bundle.objects.count(), 1)
+
+class BundleAddFromListTest(BundleTestBase):
+    def testAddToEmptyBundle(self):
+        patch = self.patches[0]
+        params = {'form': 'patchlistform',
+                  'action': 'Add',
+                  'project': defaults.project.id,
+                  'bundle_id': self.bundle.id,
+                  'patch_id:%d' % patch.id: 'checked'}
+
+        response = self.client.post(
+                '/project/%s/list/' % defaults.project.linkname,
+                params)
+
+        self.assertContains(response, 'added to bundle %s' % self.bundle.name,
+            count = 1)
+
+        self.failUnlessEqual(self.bundle.patches.count(), 1)
+        self.failUnlessEqual(self.bundle.patches.all()[0], patch)
+
+    def testAddToNonEmptyBundle(self):
+        self.bundle.append_patch(self.patches[0])
+        patch = self.patches[1]
+        params = {'form': 'patchlistform',
+                  'action': 'Add',
+                  'project': defaults.project.id,
+                  'bundle_id': self.bundle.id,
+                  'patch_id:%d' % patch.id: 'checked'}
+
+        response = self.client.post(
+                '/project/%s/list/' % defaults.project.linkname,
+                params)
+
+        self.assertContains(response, 'added to bundle %s' % self.bundle.name,
+            count = 1)
+
+        self.failUnlessEqual(self.bundle.patches.count(), 2)
+        self.failUnless(self.patches[0] in self.bundle.patches.all())
+        self.failUnless(self.patches[1] in self.bundle.patches.all())
+
+        # check order
+        bps = [ BundlePatch.objects.get(bundle = self.bundle,
+                                        patch = self.patches[i]) \
+                for i in [0, 1] ]
+        self.failUnless(bps[0].order < bps[1].order)
+
+    def testAddDuplicate(self):
+        self.bundle.append_patch(self.patches[0])
+        count = self.bundle.patches.count()
+        patch = self.patches[0]
+
+        params = {'form': 'patchlistform',
+                  'action': 'Add',
+                  'project': defaults.project.id,
+                  'bundle_id': self.bundle.id,
+                  'patch_id:%d' % patch.id: 'checked'}
+
+        response = self.client.post(
+                '/project/%s/list/' % defaults.project.linkname,
+                params)
+
+        self.assertContains(response, 'Patch &#39;%s&#39; already in bundle' \
+                            % patch.name, count = 1, status_code = 200)
+
+        self.assertEquals(count, self.bundle.patches.count())
+
+    def testAddNewAndDuplicate(self):
+        self.bundle.append_patch(self.patches[0])
+        count = self.bundle.patches.count()
+        patch = self.patches[0]
+
+        params = {'form': 'patchlistform',
+                  'action': 'Add',
+                  'project': defaults.project.id,
+                  'bundle_id': self.bundle.id,
+                  'patch_id:%d' % patch.id: 'checked',
+                  'patch_id:%d' % self.patches[1].id: 'checked'}
+
+        response = self.client.post(
+                '/project/%s/list/' % defaults.project.linkname,
+                params)
+
+        self.assertContains(response, 'Patch &#39;%s&#39; already in bundle' \
+                            % patch.name, count = 1, status_code = 200)
+        self.assertContains(response, 'Patch &#39;%s&#39; added to bundle' \
+                            % self.patches[1].name, count = 1,
+                            status_code = 200)
+        self.assertEquals(count + 1, self.bundle.patches.count())
+
+class BundleAddFromPatchTest(BundleTestBase):
+    def testAddToEmptyBundle(self):
+        patch = self.patches[0]
+        params = {'action': 'addtobundle',
+                  'bundle_id': self.bundle.id}
+
+        response = self.client.post('/patch/%d/' % patch.id, params)
+
+        self.assertContains(response,
+                'added to bundle &quot;%s&quot;' % self.bundle.name,
+                count = 1)
+
+        self.failUnlessEqual(self.bundle.patches.count(), 1)
+        self.failUnlessEqual(self.bundle.patches.all()[0], patch)
+
+    def testAddToNonEmptyBundle(self):
+        self.bundle.append_patch(self.patches[0])
+        patch = self.patches[1]
+        params = {'action': 'addtobundle',
+                  'bundle_id': self.bundle.id}
+
+        response = self.client.post('/patch/%d/' % patch.id, params)
+
+        self.assertContains(response,
+                'added to bundle &quot;%s&quot;' % self.bundle.name,
+                count = 1)
+
+        self.failUnlessEqual(self.bundle.patches.count(), 2)
+        self.failUnless(self.patches[0] in self.bundle.patches.all())
+        self.failUnless(self.patches[1] in self.bundle.patches.all())
+
+        # check order
+        bps = [ BundlePatch.objects.get(bundle = self.bundle,
+                                        patch = self.patches[i]) \
+                for i in [0, 1] ]
+        self.failUnless(bps[0].order < bps[1].order)
+
+class BundleInitialOrderTest(BundleTestBase):
+    """When creating bundles from a patch list, ensure that the patches in the
+       bundle are ordered by date"""
+
+    def setUp(self):
+        super(BundleInitialOrderTest, self).setUp(5)
+
+        # put patches in an arbitrary order
+        idxs = [2, 4, 3, 1, 0]
+        self.patches = [ self.patches[i] for i in idxs ]
+
+        # set dates to be sequential
+        last_patch = self.patches[0]
+        for patch in self.patches[1:]:
+            patch.date = last_patch.date + datetime.timedelta(0, 1)
+            patch.save()
+            last_patch = patch
+
+    def _testOrder(self, ids, expected_order):
+        newbundlename = 'testbundle-new'
+
+        # need to define our querystring explicity to enforce ordering
+        params = {'form': 'patchlistform',
+                  'bundle_name': newbundlename,
+                  'action': 'Create',
+                  'project': defaults.project.id,
+        }
+
+        data = urlencode(params) + \
+               ''.join([ '&patch_id:%d=checked' % i for i in ids ])
+
+        response = self.client.post(
+                '/project/%s/list/' % defaults.project.linkname,
+                data = data,
+                content_type = 'application/x-www-form-urlencoded',
+                )
+
+        self.assertContains(response, 'Bundle %s created' % newbundlename)
+        self.assertContains(response, 'added to bundle %s' % newbundlename,
+            count = 5)
+
+        bundle = Bundle.objects.get(name = newbundlename)
+
+        # BundlePatches should be sorted by .order by default
+        bps = BundlePatch.objects.filter(bundle = bundle)
+
+        for (bp, p) in zip(bps, expected_order):
+            self.assertEqual(bp.patch.pk, p.pk)
+
+        bundle.delete()
+
+    def testBundleForwardOrder(self):
+        ids = map(lambda p: p.id, self.patches)
+        self._testOrder(ids, self.patches)
+
+    def testBundleReverseOrder(self):
+        ids = map(lambda p: p.id, self.patches)
+        ids.reverse()
+        self._testOrder(ids, self.patches)
+
+class BundleReorderTest(BundleTestBase):
+    def setUp(self):
+        super(BundleReorderTest, self).setUp(5)
+        for i in range(5):
+            self.bundle.append_patch(self.patches[i])
+
+    def checkReordering(self, neworder, start, end):
+        neworder_ids = [ self.patches[i].id for i in neworder ]
+
+        firstpatch = BundlePatch.objects.get(bundle = self.bundle,
+                patch = self.patches[start]).patch
+
+        slice_ids = neworder_ids[start:end]
+        params = {'form': 'reorderform',
+                  'order_start': firstpatch.id,
+                  'neworder': slice_ids}
+
+        response = self.client.post(bundle_url(self.bundle), params)
+
+        self.failUnlessEqual(response.status_code, 200)
+
+        bps = BundlePatch.objects.filter(bundle = self.bundle) \
+                        .order_by('order')
+
+        # check if patch IDs are in the expected order:
+        bundle_ids = [ bp.patch.id for bp in bps ]
+        self.failUnlessEqual(neworder_ids, bundle_ids)
+
+        # check if order field is still sequential:
+        order_numbers = [ bp.order for bp in bps ]
+        expected_order = range(1, len(neworder)+1) # [1 ... len(neworder)]
+        self.failUnlessEqual(order_numbers, expected_order)
+
+    def testBundleReorderAll(self):
+        # reorder all patches:
+        self.checkReordering([2,1,4,0,3], 0, 5)
+
+    def testBundleReorderEnd(self):
+        # reorder only the last three patches
+        self.checkReordering([0,1,3,2,4], 2, 5)
+
+    def testBundleReorderBegin(self):
+        # reorder only the first three patches
+        self.checkReordering([2,0,1,3,4], 0, 3)
+
+    def testBundleReorderMiddle(self):
+        # reorder only 2nd, 3rd, and 4th patches
+        self.checkReordering([0,2,3,1,4], 1, 4)
+
+class BundleRedirTest(BundleTestBase):
+    # old URL: private bundles used to be under /user/bundle/<id>
+
+    def setUp(self):
+        super(BundleRedirTest, self).setUp()
+
+    @unittest.skipIf(not settings.COMPAT_REDIR, "compat redirections disabled")
+    def testBundleRedir(self):
+        url = '/user/bundle/%d/' % self.bundle.id
+        response = self.client.get(url)
+        self.assertRedirects(response, bundle_url(self.bundle))
+
+    @unittest.skipIf(not settings.COMPAT_REDIR, "compat redirections disabled")
+    def testMboxRedir(self):
+        url = '/user/bundle/%d/mbox/' % self.bundle.id
+        response = self.client.get(url)
+        self.assertRedirects(response,'/bundle/%s/%s/mbox/' %
+                                        (self.bundle.owner.username,
+                                         self.bundle.name))
diff --git a/apps/patchwork/tests/test_confirm.py b/apps/patchwork/tests/test_confirm.py
new file mode 100644 (file)
index 0000000..fad5125
--- /dev/null
@@ -0,0 +1,67 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2011 Jeremy Kerr <jk@ozlabs.org>
+#
+# This file is part of the Patchwork package.
+#
+# Patchwork is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# Patchwork is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Patchwork; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+import unittest
+from django.test import TestCase
+from django.contrib.auth.models import User
+from django.core.urlresolvers import reverse
+from patchwork.models import EmailConfirmation, Person
+
+def _confirmation_url(conf):
+    return reverse('patchwork.views.confirm', kwargs = {'key': conf.key})
+
+class TestUser(object):
+    username = 'testuser'
+    email = 'test@example.com'
+    secondary_email = 'test2@example.com'
+    password = None
+
+    def __init__(self):
+        self.password = User.objects.make_random_password()
+        self.user = User.objects.create_user(self.username,
+                            self.email, self.password)
+
+class InvalidConfirmationTest(TestCase):
+    def setUp(self):
+        EmailConfirmation.objects.all().delete()
+        Person.objects.all().delete()
+        self.user = TestUser()
+        self.conf = EmailConfirmation(type = 'userperson',
+                                      email = self.user.secondary_email,
+                                      user = self.user.user)
+        self.conf.save()
+
+    def testInactiveConfirmation(self):
+        self.conf.active = False
+        self.conf.save()
+        response = self.client.get(_confirmation_url(self.conf))
+        self.assertEquals(response.status_code, 200)
+        self.assertTemplateUsed(response, 'patchwork/confirm-error.html')
+        self.assertEqual(response.context['error'], 'inactive')
+        self.assertEqual(response.context['conf'], self.conf)
+
+    def testExpiredConfirmation(self):
+        self.conf.date -= self.conf.validity
+        self.conf.save()
+        response = self.client.get(_confirmation_url(self.conf))
+        self.assertEquals(response.status_code, 200)
+        self.assertTemplateUsed(response, 'patchwork/confirm-error.html')
+        self.assertEqual(response.context['error'], 'expired')
+        self.assertEqual(response.context['conf'], self.conf)
+
diff --git a/apps/patchwork/tests/test_encodings.py b/apps/patchwork/tests/test_encodings.py
new file mode 100644 (file)
index 0000000..b9032bb
--- /dev/null
@@ -0,0 +1,87 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
+#
+# This file is part of the Patchwork package.
+#
+# Patchwork is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# Patchwork is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Patchwork; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+import unittest
+import os
+import time
+from patchwork.models import Patch, Person
+from patchwork.tests.utils import defaults, read_patch
+from django.test import TestCase
+from django.test.client import Client
+
+class UTF8PatchViewTest(TestCase):
+    patch_filename = '0002-utf-8.patch'
+    patch_encoding = 'utf-8'
+
+    def setUp(self):
+        defaults.project.save()
+        defaults.patch_author_person.save()
+        self.patch_content = read_patch(self.patch_filename,
+                encoding = self.patch_encoding)
+        self.patch = Patch(project = defaults.project,
+                           msgid = 'x', name = defaults.patch_name,
+                           submitter = defaults.patch_author_person,
+                           content = self.patch_content)
+        self.patch.save()
+        self.client = Client()
+
+    def testPatchView(self):
+        response = self.client.get('/patch/%d/' % self.patch.id)
+        self.assertContains(response, self.patch.name)
+
+    def testMboxView(self):
+        response = self.client.get('/patch/%d/mbox/' % self.patch.id)
+        self.assertEquals(response.status_code, 200)
+        self.assertTrue(self.patch.content in \
+                response.content.decode(self.patch_encoding))
+
+    def testRawView(self):
+        response = self.client.get('/patch/%d/raw/' % self.patch.id)
+        self.assertEquals(response.status_code, 200)
+        self.assertEquals(response.content.decode(self.patch_encoding),
+                self.patch.content)
+
+    def tearDown(self):
+        self.patch.delete()
+        defaults.patch_author_person.delete()
+        defaults.project.delete()
+
+class UTF8HeaderPatchViewTest(UTF8PatchViewTest):
+    patch_filename = '0002-utf-8.patch'
+    patch_encoding = 'utf-8'
+    patch_author_name = u'P\xe4tch Author'
+
+    def setUp(self):
+        defaults.project.save()
+        self.patch_author = Person(name = self.patch_author_name,
+            email = defaults.patch_author_person.email)
+        self.patch_author.save()
+        self.patch_content = read_patch(self.patch_filename,
+                encoding = self.patch_encoding)
+        self.patch = Patch(project = defaults.project,
+                           msgid = 'x', name = defaults.patch_name,
+                           submitter = self.patch_author,
+                           content = self.patch_content)
+        self.patch.save()
+        self.client = Client()
+
+    def tearDown(self):
+        self.patch.delete()
+        self.patch_author.delete()
+        defaults.project.delete()
diff --git a/apps/patchwork/tests/test_expiry.py b/apps/patchwork/tests/test_expiry.py
new file mode 100644 (file)
index 0000000..844ed4b
--- /dev/null
@@ -0,0 +1,121 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2014 Jeremy Kerr <jk@ozlabs.org>
+#
+# This file is part of the Patchwork package.
+#
+# Patchwork is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# Patchwork is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Patchwork; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+import unittest
+import datetime
+from django.test import TestCase
+from django.contrib.auth.models import User
+from patchwork.models import EmailConfirmation, Person, Patch
+from patchwork.tests.utils import create_user, defaults
+from patchwork.utils import do_expiry
+
+class TestRegistrationExpiry(TestCase):
+
+    def register(self, date):
+        user = create_user()
+        user.is_active = False
+        user.date_joined = user.last_login = date
+        user.save()
+
+        conf = EmailConfirmation(type='registration', user=user,
+                                    email=user.email)
+        conf.date = date
+        conf.save()
+
+        return (user, conf)
+
+    def testOldRegistrationExpiry(self):
+        date = ((datetime.datetime.now() - EmailConfirmation.validity) -
+                datetime.timedelta(hours = 1))
+        (user, conf) = self.register(date)
+
+        do_expiry()
+
+        self.assertFalse(User.objects.filter(pk = user.pk).exists())
+        self.assertFalse(EmailConfirmation.objects.filter(pk = conf.pk)
+                            .exists())
+
+
+    def testRecentRegistrationExpiry(self):
+        date = ((datetime.datetime.now() - EmailConfirmation.validity) +
+                datetime.timedelta(hours = 1))
+        (user, conf) = self.register(date)
+
+        do_expiry()
+
+        self.assertTrue(User.objects.filter(pk = user.pk).exists())
+        self.assertTrue(EmailConfirmation.objects.filter(pk = conf.pk)
+                            .exists())
+
+    def testInactiveRegistrationExpiry(self):
+        (user, conf) = self.register(datetime.datetime.now())
+
+        # confirm registration
+        conf.user.is_active = True
+        conf.user.save()
+        conf.deactivate()
+
+        do_expiry()
+
+        self.assertTrue(User.objects.filter(pk = user.pk).exists())
+        self.assertFalse(EmailConfirmation.objects.filter(pk = conf.pk)
+                            .exists())
+
+    def testPatchSubmitterExpiry(self):
+        defaults.project.save()
+        defaults.patch_author_person.save()
+
+        # someone submits a patch...
+        patch = Patch(project = defaults.project,
+                    msgid = 'test@example.com', name = 'test patch',
+                    submitter = defaults.patch_author_person,
+                    content = defaults.patch)
+        patch.save()
+
+        # ... then starts registration...
+        date = ((datetime.datetime.now() - EmailConfirmation.validity) -
+                datetime.timedelta(hours = 1))
+        userid = 'test-user'
+        user = User.objects.create_user(userid,
+                defaults.patch_author_person.email, userid)
+        user.is_active = False
+        user.date_joined = user.last_login = date
+        user.save()
+
+        self.assertEqual(user.email, patch.submitter.email)
+
+        conf = EmailConfirmation(type='registration', user=user,
+                                    email=user.email)
+        conf.date = date
+        conf.save()
+
+        # ... which expires
+        do_expiry()
+
+        # we should see no matching user
+        self.assertFalse(User.objects.filter(email = patch.submitter.email)
+                            .exists())
+        # but the patch and person should still be present
+        self.assertTrue(Person.objects.filter(
+                            pk = defaults.patch_author_person.pk).exists())
+        self.assertTrue(Patch.objects.filter(pk = patch.pk).exists())
+
+        # and there should be no user associated with the person
+        self.assertEqual(Person.objects.get(pk =
+                    defaults.patch_author_person.pk).user, None)
diff --git a/apps/patchwork/tests/test_filters.py b/apps/patchwork/tests/test_filters.py
new file mode 100644 (file)
index 0000000..2c464e5
--- /dev/null
@@ -0,0 +1,45 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2011 Jeremy Kerr <jk@ozlabs.org>
+#
+# This file is part of the Patchwork package.
+#
+# Patchwork is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# Patchwork is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Patchwork; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+import unittest
+from django.test import TestCase
+from django.test.client import Client
+from patchwork.tests.utils import defaults, create_user, find_in_context
+
+class FilterQueryStringTest(TestCase):
+    def testFilterQSEscaping(self):
+        """test that filter fragments in a query string are properly escaped,
+           and stray ampersands don't get reflected back in the filter
+           links"""
+        project = defaults.project
+        defaults.project.save()
+        url = '/project/%s/list/?submitter=a%%26b=c' % project.linkname
+        response = self.client.get(url)
+        self.failUnlessEqual(response.status_code, 200)
+        self.failIf('submitter=a&amp;b=c' in response.content)
+        self.failIf('submitter=a&b=c' in response.content)
+
+    def testUTF8QSHandling(self):
+        """test that non-ascii characters can be handled by the filter
+           code"""
+        project = defaults.project
+        defaults.project.save()
+        url = '/project/%s/list/?submitter=%%E2%%98%%83' % project.linkname
+        response = self.client.get(url)
+        self.failUnlessEqual(response.status_code, 200)
diff --git a/apps/patchwork/tests/test_list.py b/apps/patchwork/tests/test_list.py
new file mode 100644 (file)
index 0000000..1bdb506
--- /dev/null
@@ -0,0 +1,38 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2012 Jeremy Kerr <jk@ozlabs.org>
+#
+# This file is part of the Patchwork package.
+#
+# Patchwork is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# Patchwork is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Patchwork; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+import unittest
+from django.test import TestCase
+from django.test.client import Client
+from patchwork.tests.utils import defaults, create_user, find_in_context
+from django.core.urlresolvers import reverse
+
+class EmptyPatchListTest(TestCase):
+
+    def testEmptyPatchList(self):
+        """test that we don't output an empty table when there are no
+           patches present"""
+        project = defaults.project
+        defaults.project.save()
+        url = reverse('patchwork.views.patch.list',
+                kwargs={'project_id': project.linkname})
+        response = self.client.get(url)
+        self.assertContains(response, 'No patches to display')
+        self.assertNotContains(response, 'tbody')
+
diff --git a/apps/patchwork/tests/test_mail_settings.py b/apps/patchwork/tests/test_mail_settings.py
new file mode 100644 (file)
index 0000000..a193c97
--- /dev/null
@@ -0,0 +1,299 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2010 Jeremy Kerr <jk@ozlabs.org>
+#
+# This file is part of the Patchwork package.
+#
+# Patchwork is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# Patchwork is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Patchwork; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+import unittest
+import re
+from django.test import TestCase
+from django.test.client import Client
+from django.core import mail
+from django.core.urlresolvers import reverse
+from django.contrib.auth.models import User
+from patchwork.models import EmailOptout, EmailConfirmation, Person
+from patchwork.tests.utils import create_user, error_strings
+
+class MailSettingsTest(TestCase):
+    view = 'patchwork.views.mail.settings'
+    url = reverse(view)
+
+    def testMailSettingsGET(self):
+        response = self.client.get(self.url)
+        self.assertEquals(response.status_code, 200)
+        self.assertTrue(response.context['form'])
+
+    def testMailSettingsPOST(self):
+        email = u'foo@example.com'
+        response = self.client.post(self.url, {'email': email})
+        self.assertEquals(response.status_code, 200)
+        self.assertTemplateUsed(response, 'patchwork/mail-settings.html')
+        self.assertEquals(response.context['email'], email)
+
+    def testMailSettingsPOSTEmpty(self):
+        response = self.client.post(self.url, {'email': ''})
+        self.assertEquals(response.status_code, 200)
+        self.assertTemplateUsed(response, 'patchwork/mail-form.html')
+        self.assertFormError(response, 'form', 'email',
+                'This field is required.')
+
+    def testMailSettingsPOSTInvalid(self):
+        response = self.client.post(self.url, {'email': 'foo'})
+        self.assertEquals(response.status_code, 200)
+        self.assertTemplateUsed(response, 'patchwork/mail-form.html')
+        self.assertFormError(response, 'form', 'email', error_strings['email'])
+
+    def testMailSettingsPOSTOptedIn(self):
+        email = u'foo@example.com'
+        response = self.client.post(self.url, {'email': email})
+        self.assertEquals(response.status_code, 200)
+        self.assertTemplateUsed(response, 'patchwork/mail-settings.html')
+        self.assertEquals(response.context['is_optout'], False)
+        self.assertTrue('<strong>may</strong>' in response.content)
+        optout_url = reverse('patchwork.views.mail.optout')
+        self.assertTrue(('action="%s"' % optout_url) in response.content)
+
+    def testMailSettingsPOSTOptedOut(self):
+        email = u'foo@example.com'
+        EmailOptout(email = email).save()
+        response = self.client.post(self.url, {'email': email})
+        self.assertEquals(response.status_code, 200)
+        self.assertTemplateUsed(response, 'patchwork/mail-settings.html')
+        self.assertEquals(response.context['is_optout'], True)
+        self.assertTrue('<strong>may not</strong>' in response.content)
+        optin_url = reverse('patchwork.views.mail.optin')
+        self.assertTrue(('action="%s"' % optin_url) in response.content)
+
+class OptoutRequestTest(TestCase):
+    view = 'patchwork.views.mail.optout'
+    url = reverse(view)
+
+    def testOptOutRequestGET(self):
+        response = self.client.get(self.url)
+        self.assertRedirects(response, reverse('patchwork.views.mail.settings'))
+
+    def testOptoutRequestValidPOST(self):
+        email = u'foo@example.com'
+        response = self.client.post(self.url, {'email': email})
+
+        # check for a confirmation object
+        self.assertEquals(EmailConfirmation.objects.count(), 1)
+        conf = EmailConfirmation.objects.get(email = email)
+
+        # check confirmation page
+        self.assertEquals(response.status_code, 200)
+        self.assertEquals(response.context['confirmation'], conf)
+        self.assertTrue(email in response.content)
+
+        # check email
+        url = reverse('patchwork.views.confirm', kwargs = {'key': conf.key})
+        self.assertEquals(len(mail.outbox), 1)
+        msg = mail.outbox[0]
+        self.assertEquals(msg.to, [email])
+        self.assertEquals(msg.subject, 'Patchwork opt-out confirmation')
+        self.assertTrue(url in msg.body)
+
+    def testOptoutRequestInvalidPOSTEmpty(self):
+        response = self.client.post(self.url, {'email': ''})
+        self.assertEquals(response.status_code, 200)
+        self.assertFormError(response, 'form', 'email',
+                'This field is required.')
+        self.assertTrue(response.context['error'])
+        self.assertTrue('email_sent' not in response.context)
+        self.assertEquals(len(mail.outbox), 0)
+
+    def testOptoutRequestInvalidPOSTNonEmail(self):
+        response = self.client.post(self.url, {'email': 'foo'})
+        self.assertEquals(response.status_code, 200)
+        self.assertFormError(response, 'form', 'email', error_strings['email'])
+        self.assertTrue(response.context['error'])
+        self.assertTrue('email_sent' not in response.context)
+        self.assertEquals(len(mail.outbox), 0)
+
+class OptoutTest(TestCase):
+    view = 'patchwork.views.mail.optout'
+    url = reverse(view)
+
+    def setUp(self):
+        self.email = u'foo@example.com'
+        self.conf = EmailConfirmation(type = 'optout', email = self.email)
+        self.conf.save()
+
+    def testOptoutValidHash(self):
+        url = reverse('patchwork.views.confirm',
+                        kwargs = {'key': self.conf.key})
+        response = self.client.get(url)
+
+        self.assertEquals(response.status_code, 200)
+        self.assertTemplateUsed(response, 'patchwork/optout.html')
+        self.assertTrue(self.email in response.content)
+
+        # check that we've got an optout in the list
+        self.assertEquals(EmailOptout.objects.count(), 1)
+        self.assertEquals(EmailOptout.objects.all()[0].email, self.email)
+
+        # check that the confirmation is now inactive
+        self.assertFalse(EmailConfirmation.objects.get(
+                                    pk = self.conf.pk).active)
+
+
+class OptoutPreexistingTest(OptoutTest):
+    """Test that a duplicated opt-out behaves the same as the initial one"""
+    def setUp(self):
+        super(OptoutPreexistingTest, self).setUp()
+        EmailOptout(email = self.email).save()
+
+class OptinRequestTest(TestCase):
+    view = 'patchwork.views.mail.optin'
+    url = reverse(view)
+
+    def setUp(self):
+        self.email = u'foo@example.com'
+        EmailOptout(email = self.email).save()
+
+    def testOptInRequestGET(self):
+        response = self.client.get(self.url)
+        self.assertRedirects(response, reverse('patchwork.views.mail.settings'))
+
+    def testOptInRequestValidPOST(self):
+        response = self.client.post(self.url, {'email': self.email})
+
+        # check for a confirmation object
+        self.assertEquals(EmailConfirmation.objects.count(), 1)
+        conf = EmailConfirmation.objects.get(email = self.email)
+
+        # check confirmation page
+        self.assertEquals(response.status_code, 200)
+        self.assertEquals(response.context['confirmation'], conf)
+        self.assertTrue(self.email in response.content)
+
+        # check email
+        url = reverse('patchwork.views.confirm', kwargs = {'key': conf.key})
+        self.assertEquals(len(mail.outbox), 1)
+        msg = mail.outbox[0]
+        self.assertEquals(msg.to, [self.email])
+        self.assertEquals(msg.subject, 'Patchwork opt-in confirmation')
+        self.assertTrue(url in msg.body)
+
+    def testOptoutRequestInvalidPOSTEmpty(self):
+        response = self.client.post(self.url, {'email': ''})
+        self.assertEquals(response.status_code, 200)
+        self.assertFormError(response, 'form', 'email',
+                'This field is required.')
+        self.assertTrue(response.context['error'])
+        self.assertTrue('email_sent' not in response.context)
+        self.assertEquals(len(mail.outbox), 0)
+
+    def testOptoutRequestInvalidPOSTNonEmail(self):
+        response = self.client.post(self.url, {'email': 'foo'})
+        self.assertEquals(response.status_code, 200)
+        self.assertFormError(response, 'form', 'email', error_strings['email'])
+        self.assertTrue(response.context['error'])
+        self.assertTrue('email_sent' not in response.context)
+        self.assertEquals(len(mail.outbox), 0)
+
+class OptinTest(TestCase):
+
+    def setUp(self):
+        self.email = u'foo@example.com'
+        self.optout = EmailOptout(email = self.email)
+        self.optout.save()
+        self.conf = EmailConfirmation(type = 'optin', email = self.email)
+        self.conf.save()
+
+    def testOptinValidHash(self):
+        url = reverse('patchwork.views.confirm',
+                        kwargs = {'key': self.conf.key})
+        response = self.client.get(url)
+
+        self.assertEquals(response.status_code, 200)
+        self.assertTemplateUsed(response, 'patchwork/optin.html')
+        self.assertTrue(self.email in response.content)
+
+        # check that there's no optout remaining
+        self.assertEquals(EmailOptout.objects.count(), 0)
+
+        # check that the confirmation is now inactive
+        self.assertFalse(EmailConfirmation.objects.get(
+                                    pk = self.conf.pk).active)
+
+class OptinWithoutOptoutTest(TestCase):
+    """Test an opt-in with no existing opt-out"""
+    view = 'patchwork.views.mail.optin'
+    url = reverse(view)
+
+    def testOptInWithoutOptout(self):
+        email = u'foo@example.com'
+        response = self.client.post(self.url, {'email': email})
+
+        # check for an error message
+        self.assertEquals(response.status_code, 200)
+        self.assertTrue(bool(response.context['error']))
+        self.assertTrue('not on the patchwork opt-out list' in response.content)
+
+class UserProfileOptoutFormTest(TestCase):
+    """Test that the correct optin/optout forms appear on the user profile
+       page, for logged-in users"""
+
+    view = 'patchwork.views.user.profile'
+    url = reverse(view)
+    optout_url = reverse('patchwork.views.mail.optout')
+    optin_url = reverse('patchwork.views.mail.optin')
+    form_re_template = ('<form\s+[^>]*action="%(url)s"[^>]*>'
+                        '.*?<input\s+[^>]*value="%(email)s"[^>]*>.*?'
+                        '</form>')
+    secondary_email = 'test2@example.com'
+
+    def setUp(self):
+        self.user = create_user()
+        self.client.login(username = self.user.username,
+                password = self.user.username)
+
+    def _form_re(self, url, email):
+        return re.compile(self.form_re_template % {'url': url, 'email': email},
+                          re.DOTALL)
+
+    def testMainEmailOptoutForm(self):
+        form_re = self._form_re(self.optout_url, self.user.email)
+        response = self.client.get(self.url)
+        self.assertEquals(response.status_code, 200)
+        self.assertTrue(form_re.search(response.content) is not None)
+
+    def testMainEmailOptinForm(self):
+        EmailOptout(email = self.user.email).save()
+        form_re = self._form_re(self.optin_url, self.user.email)
+        response = self.client.get(self.url)
+        self.assertEquals(response.status_code, 200)
+        self.assertTrue(form_re.search(response.content) is not None)
+
+    def testSecondaryEmailOptoutForm(self):
+        p = Person(email = self.secondary_email, user = self.user)
+        p.save()
+        
+        form_re = self._form_re(self.optout_url, p.email)
+        response = self.client.get(self.url)
+        self.assertEquals(response.status_code, 200)
+        self.assertTrue(form_re.search(response.content) is not None)
+
+    def testSecondaryEmailOptinForm(self):
+        p = Person(email = self.secondary_email, user = self.user)
+        p.save()
+        EmailOptout(email = p.email).save()
+
+        form_re = self._form_re(self.optin_url, p.email)
+        response = self.client.get(self.url)
+        self.assertEquals(response.status_code, 200)
+        self.assertTrue(form_re.search(response.content) is not None)
diff --git a/apps/patchwork/tests/test_mboxviews.py b/apps/patchwork/tests/test_mboxviews.py
new file mode 100644 (file)
index 0000000..6209513
--- /dev/null
@@ -0,0 +1,181 @@
+# vim: set fileencoding=utf-8 :
+#
+# Patchwork - automated patch tracking system
+# Copyright (C) 2009 Jeremy Kerr <jk@ozlabs.org>
+#
+# This file is part of the Patchwork package.
+#
+# Patchwork is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# Patchwork is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Patchwork; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+import unittest
+import email
+import datetime
+import dateutil.parser, dateutil.tz
+from django.test import TestCase
+from django.test.client import Client
+from patchwork.models import Patch, Comment, Person
+from patchwork.tests.utils import defaults, create_user, find_in_context
+
+class MboxPatchResponseTest(TestCase):
+    """ Test that the mbox view appends the Acked-by from a patch comment """
+    def setUp(self):
+        defaults.project.save()
+
+        self.person = defaults.patch_author_person
+        self.person.save()
+
+        self.patch = Patch(project = defaults.project,
+                           msgid = 'p1', name = 'testpatch',
+                           submitter = self.person, content = '')
+        self.patch.save()
+        comment = Comment(patch = self.patch, msgid = 'p1',
+                submitter = self.person,
+                content = 'comment 1 text\nAcked-by: 1\n')
+        comment.save()
+
+        comment = Comment(patch = self.patch, msgid = 'p2',
+                submitter = self.person,
+                content = 'comment 2 text\nAcked-by: 2\n')
+        comment.save()
+
+    def testPatchResponse(self):
+        response = self.client.get('/patch/%d/mbox/' % self.patch.id)
+        self.assertContains(response,
+                'Acked-by: 1\nAcked-by: 2\n')
+
+class MboxPatchSplitResponseTest(TestCase):
+    """ Test that the mbox view appends the Acked-by from a patch comment,
+        and places it before an '---' update line. """
+    def setUp(self):
+        defaults.project.save()
+
+        self.person = defaults.patch_author_person
+        self.person.save()
+
+        self.patch = Patch(project = defaults.project,
+                           msgid = 'p1', name = 'testpatch',
+                           submitter = self.person, content = '')
+        self.patch.save()
+        comment = Comment(patch = self.patch, msgid = 'p1',
+                submitter = self.person,
+                content = 'comment 1 text\nAcked-by: 1\n---\nupdate\n')
+        comment.save()
+
+        comment = Comment(patch = self.patch, msgid = 'p2',
+                submitter = self.person,
+                content = 'comment 2 text\nAcked-by: 2\n')
+        comment.save()
+
+    def testPatchResponse(self):
+        response = self.client.get('/patch/%d/mbox/' % self.patch.id)
+        self.assertContains(response,
+                'Acked-by: 1\nAcked-by: 2\n')
+
+class MboxPassThroughHeaderTest(TestCase):
+    """ Test that we see 'Cc' and 'To' headers passed through from original
+        message to mbox view """
+
+    def setUp(self):
+        defaults.project.save()
+        self.person = defaults.patch_author_person
+        self.person.save()
+
+        self.cc_header = 'Cc: CC Person <cc@example.com>'
+        self.to_header = 'To: To Person <to@example.com>'
+        self.date_header = 'Date: Fri, 7 Jun 2013 15:42:54 +1000'
+
+        self.patch = Patch(project = defaults.project,
+                           msgid = 'p1', name = 'testpatch',
+                           submitter = self.person, content = '')
+
+    def testCCHeader(self):
+        self.patch.headers = self.cc_header + '\n'
+        self.patch.save()
+
+        response = self.client.get('/patch/%d/mbox/' % self.patch.id)
+        self.assertContains(response, self.cc_header)
+
+    def testToHeader(self):
+        self.patch.headers = self.to_header + '\n'
+        self.patch.save()
+
+        response = self.client.get('/patch/%d/mbox/' % self.patch.id)
+        self.assertContains(response, self.to_header)
+
+    def testDateHeader(self):
+        self.patch.headers = self.date_header + '\n'
+        self.patch.save()
+
+        response = self.client.get('/patch/%d/mbox/' % self.patch.id)
+        self.assertContains(response, self.date_header)
+
+class MboxBrokenFromHeaderTest(TestCase):
+    """ Test that a person with characters outside ASCII in his name do
+        produce correct From header. As RFC 2822 state we must retain the
+        <user@doamin.tld> format for the mail while the name part may be coded
+        in some ways. """
+
+    def setUp(self):
+        defaults.project.save()
+        self.person = defaults.patch_author_person
+        self.person.name = u'©ool guÅ·'
+        self.person.save()
+
+        self.patch = Patch(project = defaults.project,
+                msgid = 'p1', name = 'testpatch',
+                submitter = self.person, content = '')
+
+    def testFromHeader(self):
+        self.patch.save()
+        from_email = '<' + self.person.email + '>'
+
+        response = self.client.get('/patch/%d/mbox/' % self.patch.id)
+        self.assertContains(response, from_email)
+
+class MboxDateHeaderTest(TestCase):
+    """ Test that the date provided in the patch mail view is correct """
+
+    def setUp(self):
+        defaults.project.save()
+        self.person = defaults.patch_author_person
+        self.person.save()
+
+        self.patch = Patch(project = defaults.project,
+                           msgid = 'p1', name = 'testpatch',
+                           submitter = self.person, content = '')
+        self.patch.save()
+
+    def testDateHeader(self):
+        response = self.client.get('/patch/%d/mbox/' % self.patch.id)
+        mail = email.message_from_string(response.content)
+        mail_date = dateutil.parser.parse(mail['Date'])
+        # patch dates are all in UTC
+        patch_date = self.patch.date.replace(tzinfo=dateutil.tz.tzutc(),
+                                            microsecond=0)
+        self.assertEqual(mail_date, patch_date)
+
+    def testSuppliedDateHeader(self):
+        hour_offset = 3
+        tz = dateutil.tz.tzoffset(None, hour_offset * 60 * 60)
+        date = datetime.datetime.utcnow() - datetime.timedelta(days = 1)
+        date = date.replace(tzinfo=tz, microsecond=0)
+
+        self.patch.headers = 'Date: %s\n' % date.strftime("%a, %d %b %Y %T %z")
+        self.patch.save()
+
+        response = self.client.get('/patch/%d/mbox/' % self.patch.id)
+        mail = email.message_from_string(response.content)
+        mail_date = dateutil.parser.parse(mail['Date'])
+        self.assertEqual(mail_date, date)
diff --git a/apps/patchwork/tests/test_notifications.py b/apps/patchwork/tests/test_notifications.py
new file mode 100644 (file)
index 0000000..df7e3f9
--- /dev/null
@@ -0,0 +1,255 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2011 Jeremy Kerr <jk@ozlabs.org>
+#
+# This file is part of the Patchwork package.
+#
+# Patchwork is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# Patchwork is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Patchwork; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+import datetime
+from django.test import TestCase
+from django.core.urlresolvers import reverse
+from django.core import mail
+from django.conf import settings
+from django.db.utils import IntegrityError
+from patchwork.models import Patch, State, PatchChangeNotification, EmailOptout
+from patchwork.tests.utils import defaults, create_maintainer
+from patchwork.utils import send_notifications
+
+class PatchNotificationModelTest(TestCase):
+    """Tests for the creation & update of the PatchChangeNotification model"""
+
+    def setUp(self):
+        self.project = defaults.project
+        self.project.send_notifications = True
+        self.project.save()
+        self.submitter = defaults.patch_author_person
+        self.submitter.save()
+        self.patch = Patch(project = self.project, msgid = 'testpatch',
+                        name = 'testpatch', content = '',
+                        submitter = self.submitter)
+
+    def tearDown(self):
+        self.patch.delete()
+        self.submitter.delete()
+        self.project.delete()
+
+    def testPatchCreation(self):
+        """Ensure we don't get a notification on create"""
+        self.patch.save()
+        self.assertEqual(PatchChangeNotification.objects.count(), 0)
+
+    def testPatchUninterestingChange(self):
+        """Ensure we don't get a notification for "uninteresting" changes"""
+        self.patch.save()
+        self.patch.archived = True
+        self.patch.save()
+        self.assertEqual(PatchChangeNotification.objects.count(), 0)
+
+    def testPatchChange(self):
+        """Ensure we get a notification for interesting patch changes"""
+        self.patch.save()
+        oldstate = self.patch.state
+        state = State.objects.exclude(pk = oldstate.pk)[0]
+
+        self.patch.state = state
+        self.patch.save()
+        self.assertEqual(PatchChangeNotification.objects.count(), 1)
+        notification = PatchChangeNotification.objects.all()[0]
+        self.assertEqual(notification.patch, self.patch)
+        self.assertEqual(notification.orig_state, oldstate)
+
+    def testNotificationCancelled(self):
+        """Ensure we cancel notifications that are no longer valid"""
+        self.patch.save()
+        oldstate = self.patch.state
+        state = State.objects.exclude(pk = oldstate.pk)[0]
+
+        self.patch.state = state
+        self.patch.save()
+        self.assertEqual(PatchChangeNotification.objects.count(), 1)
+
+        self.patch.state = oldstate
+        self.patch.save()
+        self.assertEqual(PatchChangeNotification.objects.count(), 0)
+
+    def testNotificationUpdated(self):
+        """Ensure we update notifications when the patch has a second change,
+           but keep the original patch details"""
+        self.patch.save()
+        oldstate = self.patch.state
+        newstates = State.objects.exclude(pk = oldstate.pk)[:2]
+
+        self.patch.state = newstates[0]
+        self.patch.save()
+        self.assertEqual(PatchChangeNotification.objects.count(), 1)
+        notification = PatchChangeNotification.objects.all()[0]
+        self.assertEqual(notification.orig_state, oldstate)
+        orig_timestamp = notification.last_modified
+                         
+        self.patch.state = newstates[1]
+        self.patch.save()
+        self.assertEqual(PatchChangeNotification.objects.count(), 1)
+        notification = PatchChangeNotification.objects.all()[0]
+        self.assertEqual(notification.orig_state, oldstate)
+        self.assertTrue(notification.last_modified > orig_timestamp)
+
+    def testProjectNotificationsDisabled(self):
+        """Ensure we don't see notifications created when a project is
+           configured not to send them"""
+        self.project.send_notifications = False
+        self.project.save()
+
+        self.patch.save()
+        oldstate = self.patch.state
+        state = State.objects.exclude(pk = oldstate.pk)[0]
+
+        self.patch.state = state
+        self.patch.save()
+        self.assertEqual(PatchChangeNotification.objects.count(), 0)
+
+class PatchNotificationEmailTest(TestCase):
+
+    def setUp(self):
+        self.project = defaults.project
+        self.project.send_notifications = True
+        self.project.save()
+        self.submitter = defaults.patch_author_person
+        self.submitter.save()
+        self.patch = Patch(project = self.project, msgid = 'testpatch',
+                        name = 'testpatch', content = '',
+                        submitter = self.submitter)
+        self.patch.save()
+
+    def tearDown(self):
+        self.patch.delete()
+        self.submitter.delete()
+        self.project.delete()
+
+    def _expireNotifications(self, **kwargs):
+        timestamp = datetime.datetime.now() - \
+                    datetime.timedelta(minutes =
+                            settings.NOTIFICATION_DELAY_MINUTES + 1)
+
+        qs = PatchChangeNotification.objects.all()
+        if kwargs:
+            qs = qs.filter(**kwargs)
+
+        qs.update(last_modified = timestamp)
+
+    def testNoNotifications(self):
+        self.assertEquals(send_notifications(), [])
+
+    def testNoReadyNotifications(self):
+        """ We shouldn't see immediate notifications"""
+        PatchChangeNotification(patch = self.patch,
+                               orig_state = self.patch.state).save()
+
+        errors = send_notifications()
+        self.assertEquals(errors, [])
+        self.assertEquals(len(mail.outbox), 0)
+
+    def testNotifications(self):
+        PatchChangeNotification(patch = self.patch,
+                               orig_state = self.patch.state).save()
+        self._expireNotifications()
+
+        errors = send_notifications()
+        self.assertEquals(errors, [])
+        self.assertEquals(len(mail.outbox), 1)
+        msg = mail.outbox[0]
+        self.assertEquals(msg.to, [self.submitter.email])
+        self.assertTrue(self.patch.get_absolute_url() in msg.body)
+
+    def testNotificationEscaping(self):
+        self.patch.name = 'Patch name with " character'
+        self.patch.save()
+        PatchChangeNotification(patch = self.patch,
+                               orig_state = self.patch.state).save()
+        self._expireNotifications()
+
+        errors = send_notifications()
+        self.assertEquals(errors, [])
+        self.assertEquals(len(mail.outbox), 1)
+        msg = mail.outbox[0]
+        self.assertEquals(msg.to, [self.submitter.email])
+        self.assertFalse('&quot;' in msg.body)
+
+    def testNotificationOptout(self):
+        """ensure opt-out addresses don't get notifications"""
+        PatchChangeNotification(patch = self.patch,
+                               orig_state = self.patch.state).save()
+        self._expireNotifications()
+
+        EmailOptout(email = self.submitter.email).save()
+
+        errors = send_notifications()
+        self.assertEquals(errors, [])
+        self.assertEquals(len(mail.outbox), 0)
+
+    def testNotificationMerge(self):
+        patches = [self.patch,
+                   Patch(project = self.project, msgid = 'testpatch-2',
+                         name = 'testpatch 2', content = '',
+                         submitter = self.submitter)]
+
+        for patch in patches:
+            patch.save()
+            PatchChangeNotification(patch = patch,
+                                   orig_state = patch.state).save()
+
+        self.assertEquals(PatchChangeNotification.objects.count(), len(patches))
+        self._expireNotifications()
+        errors = send_notifications()
+        self.assertEquals(errors, [])
+        self.assertEquals(len(mail.outbox), 1)
+        msg = mail.outbox[0]
+        self.assertTrue(patches[0].get_absolute_url() in msg.body)
+        self.assertTrue(patches[1].get_absolute_url() in msg.body)
+
+    def testUnexpiredNotificationMerge(self):
+        """Test that when there are multiple pending notifications, with
+           at least one within the notification delay, that other notifications
+           are held"""
+        patches = [self.patch,
+                   Patch(project = self.project, msgid = 'testpatch-2',
+                         name = 'testpatch 2', content = '',
+                         submitter = self.submitter)]
+
+        for patch in patches:
+            patch.save()
+            PatchChangeNotification(patch = patch,
+                                   orig_state = patch.state).save()
+
+        self.assertEquals(PatchChangeNotification.objects.count(), len(patches))
+        self._expireNotifications()
+
+        # update one notification, to bring it out of the notification delay
+        patches[0].state = State.objects.exclude(pk = patches[0].state.pk)[0]
+        patches[0].save()
+
+        # the updated notification should prevent the other from being sent
+        errors = send_notifications()
+        self.assertEquals(errors, [])
+        self.assertEquals(len(mail.outbox), 0)
+
+        # expire the updated notification
+        self._expireNotifications()
+
+        errors = send_notifications()
+        self.assertEquals(errors, [])
+        self.assertEquals(len(mail.outbox), 1)
+        msg = mail.outbox[0]
+        self.assertTrue(patches[0].get_absolute_url() in msg.body)
+        self.assertTrue(patches[1].get_absolute_url() in msg.body)
diff --git a/apps/patchwork/tests/test_patchparser.py b/apps/patchwork/tests/test_patchparser.py
new file mode 100644 (file)
index 0000000..0496a69
--- /dev/null
@@ -0,0 +1,528 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
+#
+# This file is part of the Patchwork package.
+#
+# Patchwork is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# Patchwork is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Patchwork; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+import os
+from email import message_from_string
+from django.test import TestCase
+from patchwork.models import Project, Person, Patch, Comment, State, \
+         get_default_initial_patch_state
+from patchwork.tests.utils import read_patch, read_mail, create_email, \
+         defaults, create_user
+
+try:
+    from email.mime.text import MIMEText
+except ImportError:
+    # Python 2.4 compatibility
+    from email.MIMEText import MIMEText
+
+class PatchTest(TestCase):
+    default_sender = defaults.sender
+    default_subject = defaults.subject
+    project = defaults.project
+
+from patchwork.bin.parsemail import find_content, find_author, find_project, \
+                                    parse_mail
+
+class InlinePatchTest(PatchTest):
+    patch_filename = '0001-add-line.patch'
+    test_comment = 'Test for attached patch'
+
+    def setUp(self):
+        self.orig_patch = read_patch(self.patch_filename)
+        email = create_email(self.test_comment + '\n' + self.orig_patch)
+        (self.patch, self.comment) = find_content(self.project, email)
+
+    def testPatchPresence(self):
+        self.assertTrue(self.patch is not None)
+
+    def testPatchContent(self):
+        self.assertEquals(self.patch.content, self.orig_patch)
+
+    def testCommentPresence(self):
+        self.assertTrue(self.comment is not None)
+
+    def testCommentContent(self):
+        self.assertEquals(self.comment.content, self.test_comment)
+
+
+class AttachmentPatchTest(InlinePatchTest):
+    patch_filename = '0001-add-line.patch'
+    test_comment = 'Test for attached patch'
+    content_subtype = 'x-patch'
+
+    def setUp(self):
+        self.orig_patch = read_patch(self.patch_filename)
+        email = create_email(self.test_comment, multipart = True)
+        attachment = MIMEText(self.orig_patch, _subtype = self.content_subtype)
+        email.attach(attachment)
+        (self.patch, self.comment) = find_content(self.project, email)
+
+class AttachmentXDiffPatchTest(AttachmentPatchTest):
+    content_subtype = 'x-diff'
+
+class UTF8InlinePatchTest(InlinePatchTest):
+    patch_filename = '0002-utf-8.patch'
+    patch_encoding = 'utf-8'
+
+    def setUp(self):
+        self.orig_patch = read_patch(self.patch_filename, self.patch_encoding)
+        email = create_email(self.test_comment + '\n' + self.orig_patch,
+                             content_encoding = self.patch_encoding)
+        (self.patch, self.comment) = find_content(self.project, email)
+
+class NoCharsetInlinePatchTest(InlinePatchTest):
+    """ Test mails with no content-type or content-encoding header """
+    patch_filename = '0001-add-line.patch'
+
+    def setUp(self):
+        self.orig_patch = read_patch(self.patch_filename)
+        email = create_email(self.test_comment + '\n' + self.orig_patch)
+        del email['Content-Type']
+        del email['Content-Transfer-Encoding']
+        (self.patch, self.comment) = find_content(self.project, email)
+
+class SignatureCommentTest(InlinePatchTest):
+    patch_filename = '0001-add-line.patch'
+    test_comment = 'Test comment\nmore comment'
+
+    def setUp(self):
+        self.orig_patch = read_patch(self.patch_filename)
+        email = create_email( \
+                self.test_comment + '\n' + \
+                '-- \nsig\n' + self.orig_patch)
+        (self.patch, self.comment) = find_content(self.project, email)
+
+
+class ListFooterTest(InlinePatchTest):
+    patch_filename = '0001-add-line.patch'
+    test_comment = 'Test comment\nmore comment'
+
+    def setUp(self):
+        self.orig_patch = read_patch(self.patch_filename)
+        email = create_email( \
+                self.test_comment + '\n' + \
+                '_______________________________________________\n' + \
+                'Linuxppc-dev mailing list\n' + \
+                self.orig_patch)
+        (self.patch, self.comment) = find_content(self.project, email)
+
+
+class DiffWordInCommentTest(InlinePatchTest):
+    test_comment = 'Lines can start with words beginning in "diff"\n' + \
+                   'difficult\nDifferent'
+
+
+class UpdateCommentTest(InlinePatchTest):
+    """ Test for '---\nUpdate: v2' style comments to patches. """
+    patch_filename = '0001-add-line.patch'
+    test_comment = 'Test comment\nmore comment\n---\nUpdate: test update'
+
+class UpdateSigCommentTest(SignatureCommentTest):
+    """ Test for '---\nUpdate: v2' style comments to patches, with a sig """
+    patch_filename = '0001-add-line.patch'
+    test_comment = 'Test comment\nmore comment\n---\nUpdate: test update'
+
+class SenderEncodingTest(TestCase):
+    sender_name = u'example user'
+    sender_email = 'user@example.com'
+    from_header = 'example user <user@example.com>'
+
+    def setUp(self):
+        mail = 'From: %s\n' % self.from_header + \
+               'Subject: test\n\n' + \
+               'test'
+        self.email = message_from_string(mail)
+        (self.person, new) = find_author(self.email)
+        self.person.save()
+
+    def tearDown(self):
+        self.person.delete()
+
+    def testName(self):
+        self.assertEquals(self.person.name, self.sender_name)
+
+    def testEmail(self):
+        self.assertEquals(self.person.email, self.sender_email)
+
+    def testDBQueryName(self):
+        db_person = Person.objects.get(name = self.sender_name)
+        self.assertEquals(self.person, db_person)
+
+    def testDBQueryEmail(self):
+        db_person = Person.objects.get(email = self.sender_email)
+        self.assertEquals(self.person, db_person)
+
+
+class SenderUTF8QPEncodingTest(SenderEncodingTest):
+    sender_name = u'\xe9xample user'
+    from_header = '=?utf-8?q?=C3=A9xample=20user?= <user@example.com>'
+
+class SenderUTF8QPSplitEncodingTest(SenderEncodingTest):
+    sender_name = u'\xe9xample user'
+    from_header = '=?utf-8?q?=C3=A9xample?= user <user@example.com>'
+
+class SenderUTF8B64EncodingTest(SenderUTF8QPEncodingTest):
+    from_header = '=?utf-8?B?w6l4YW1wbGUgdXNlcg==?= <user@example.com>'
+
+class SubjectEncodingTest(PatchTest):
+    sender = 'example user <user@example.com>'
+    subject = 'test subject'
+    subject_header = 'test subject'
+
+    def setUp(self):
+        mail = 'From: %s\n' % self.sender + \
+               'Subject: %s\n\n' % self.subject_header + \
+               'test\n\n' + defaults.patch
+        self.projects = defaults.project
+        self.email = message_from_string(mail)
+
+    def testSubjectEncoding(self):
+        (patch, comment) = find_content(self.project, self.email)
+        self.assertEquals(patch.name, self.subject)
+
+class SubjectUTF8QPEncodingTest(SubjectEncodingTest):
+    subject = u'test s\xfcbject'
+    subject_header = '=?utf-8?q?test=20s=c3=bcbject?='
+
+class SubjectUTF8QPMultipleEncodingTest(SubjectEncodingTest):
+    subject = u'test s\xfcbject'
+    subject_header = 'test =?utf-8?q?s=c3=bcbject?='
+
+class SenderCorrelationTest(TestCase):
+    existing_sender = 'Existing Sender <existing@example.com>'
+    non_existing_sender = 'Non-existing Sender <nonexisting@example.com>'
+
+    def mail(self, sender):
+        return message_from_string('From: %s\nSubject: Test\n\ntest\n' % sender)
+
+    def setUp(self):
+        self.existing_sender_mail = self.mail(self.existing_sender)
+        self.non_existing_sender_mail = self.mail(self.non_existing_sender)
+        (self.person, new) = find_author(self.existing_sender_mail)
+        self.person.save()
+
+    def testExisingSender(self):
+        (person, new) = find_author(self.existing_sender_mail)
+        self.assertEqual(new, False)
+        self.assertEqual(person.id, self.person.id)
+
+    def testNonExisingSender(self):
+        (person, new) = find_author(self.non_existing_sender_mail)
+        self.assertEqual(new, True)
+        self.assertEqual(person.id, None)
+
+    def testExistingDifferentFormat(self):
+        mail = self.mail('existing@example.com')
+        (person, new) = find_author(mail)
+        self.assertEqual(new, False)
+        self.assertEqual(person.id, self.person.id)
+
+    def testExistingDifferentCase(self):
+        mail = self.mail(self.existing_sender.upper())
+        (person, new) = find_author(mail)
+        self.assertEqual(new, False)
+        self.assertEqual(person.id, self.person.id)
+
+    def tearDown(self):
+        self.person.delete()
+
+class MultipleProjectPatchTest(TestCase):
+    """ Test that patches sent to multiple patchwork projects are
+        handled correctly """
+
+    test_comment = 'Test Comment'
+    patch_filename = '0001-add-line.patch'
+    msgid = '<1@example.com>'
+
+    def setUp(self):
+        self.p1 = Project(linkname = 'test-project-1', name = 'Project 1',
+                listid = '1.example.com', listemail='1@example.com')
+        self.p2 = Project(linkname = 'test-project-2', name = 'Project 2',
+                listid = '2.example.com', listemail='2@example.com')
+
+        self.p1.save()
+        self.p2.save()
+
+        patch = read_patch(self.patch_filename)
+        email = create_email(self.test_comment + '\n' + patch)
+        email['Message-Id'] = self.msgid
+
+        del email['List-ID']
+        email['List-ID'] = '<' + self.p1.listid + '>'
+        parse_mail(email)
+
+        del email['List-ID']
+        email['List-ID'] = '<' + self.p2.listid + '>'
+        parse_mail(email)
+
+    def testParsedProjects(self):
+        self.assertEquals(Patch.objects.filter(project = self.p1).count(), 1)
+        self.assertEquals(Patch.objects.filter(project = self.p2).count(), 1)
+
+    def tearDown(self):
+        self.p1.delete()
+        self.p2.delete()
+
+
+class MultipleProjectPatchCommentTest(MultipleProjectPatchTest):
+    """ Test that followups to multiple-project patches end up on the
+        correct patch """
+
+    comment_msgid = '<2@example.com>'
+    comment_content = 'test comment'
+
+    def setUp(self):
+        super(MultipleProjectPatchCommentTest, self).setUp()
+
+        for project in [self.p1, self.p2]:
+            email = MIMEText(self.comment_content)
+            email['From'] = defaults.sender
+            email['Subject'] = defaults.subject
+            email['Message-Id'] = self.comment_msgid
+            email['List-ID'] = '<' + project.listid + '>'
+            email['In-Reply-To'] = self.msgid
+            parse_mail(email)
+
+    def testParsedComment(self):
+        for project in [self.p1, self.p2]:
+            patch = Patch.objects.filter(project = project)[0]
+            # we should see two comments now - the original mail with the patch,
+            # and the one we parsed in setUp()
+            self.assertEquals(Comment.objects.filter(patch = patch).count(), 2)
+
+class ListIdHeaderTest(TestCase):
+    """ Test that we parse List-Id headers from mails correctly """
+    def setUp(self):
+        self.project = Project(linkname = 'test-project-1', name = 'Project 1',
+                listid = '1.example.com', listemail='1@example.com')
+        self.project.save()
+
+    def testNoListId(self):
+        email = MIMEText('')
+        project = find_project(email)
+        self.assertEquals(project, None)
+
+    def testBlankListId(self):
+        email = MIMEText('')
+        email['List-Id'] = ''
+        project = find_project(email)
+        self.assertEquals(project, None)
+
+    def testWhitespaceListId(self):
+        email = MIMEText('')
+        email['List-Id'] = ' '
+        project = find_project(email)
+        self.assertEquals(project, None)
+
+    def testSubstringListId(self):
+        email = MIMEText('')
+        email['List-Id'] = 'example.com'
+        project = find_project(email)
+        self.assertEquals(project, None)
+
+    def testShortListId(self):
+        """ Some mailing lists have List-Id headers in short formats, where it
+            is only the list ID itself (without enclosing angle-brackets). """
+        email = MIMEText('')
+        email['List-Id'] = self.project.listid
+        project = find_project(email)
+        self.assertEquals(project, self.project)
+
+    def testLongListId(self):
+        email = MIMEText('')
+        email['List-Id'] = 'Test text <%s>' % self.project.listid
+        project = find_project(email)
+        self.assertEquals(project, self.project)
+
+    def tearDown(self):
+        self.project.delete()
+
+class MBoxPatchTest(PatchTest):
+    def setUp(self):
+        self.mail = read_mail(self.mail_file, project = self.project)
+
+class GitPullTest(MBoxPatchTest):
+    mail_file = '0001-git-pull-request.mbox'
+
+    def testGitPullRequest(self):
+        (patch, comment) = find_content(self.project, self.mail)
+        self.assertTrue(patch is not None)
+        self.assertTrue(patch.pull_url is not None)
+        self.assertTrue(patch.content is None)
+        self.assertTrue(comment is not None)
+
+class GitPullWrappedTest(GitPullTest):
+    mail_file = '0002-git-pull-request-wrapped.mbox'
+
+class GitPullWithDiffTest(MBoxPatchTest):
+    mail_file = '0003-git-pull-request-with-diff.mbox'
+
+    def testGitPullWithDiff(self):
+        (patch, comment) = find_content(self.project, self.mail)
+        self.assertTrue(patch is not None)
+        self.assertEqual('git://git.kernel.org/pub/scm/linux/kernel/git/tip/' +
+             'linux-2.6-tip.git x86-fixes-for-linus', patch.pull_url)
+        self.assertTrue(
+            patch.content.startswith('diff --git a/arch/x86/include/asm/smp.h'),
+            patch.content)
+        self.assertTrue(comment is not None)
+
+class GitPullGitSSHUrlTest(GitPullTest):
+    mail_file = '0004-git-pull-request-git+ssh.mbox'
+
+class GitPullSSHUrlTest(GitPullTest):
+    mail_file = '0005-git-pull-request-ssh.mbox'
+
+class GitPullHTTPUrlTest(GitPullTest):
+    mail_file = '0006-git-pull-request-http.mbox'
+
+class GitRenameOnlyTest(MBoxPatchTest):
+    mail_file = '0008-git-rename.mbox'
+
+    def testGitRename(self):
+        (patch, comment) = find_content(self.project, self.mail)
+        self.assertTrue(patch is not None)
+        self.assertTrue(comment is not None)
+        self.assertEqual(patch.content.count("\nrename from "), 2)
+        self.assertEqual(patch.content.count("\nrename to "), 2)
+
+class GitRenameWithDiffTest(MBoxPatchTest):
+    mail_file = '0009-git-rename-with-diff.mbox'
+
+    def testGitRename(self):
+        (patch, comment) = find_content(self.project, self.mail)
+        self.assertTrue(patch is not None)
+        self.assertTrue(comment is not None)
+        self.assertEqual(patch.content.count("\nrename from "), 2)
+        self.assertEqual(patch.content.count("\nrename to "), 2)
+        self.assertEqual(patch.content.count('\n-a\n+b'), 1)
+
+class CVSFormatPatchTest(MBoxPatchTest):
+    mail_file = '0007-cvs-format-diff.mbox'
+
+    def testPatch(self):
+        (patch, comment) = find_content(self.project, self.mail)
+        self.assertTrue(patch is not None)
+        self.assertTrue(comment is not None)
+        self.assertTrue(patch.content.startswith('Index'))
+
+class DelegateRequestTest(TestCase):
+    patch_filename = '0001-add-line.patch'
+    msgid = '<1@example.com>'
+    invalid_delegate_email = "nobody"
+
+    def setUp(self):
+        self.patch = read_patch(self.patch_filename)
+        self.user = create_user()
+        self.p1 = Project(linkname = 'test-project-1', name = 'Project 1',
+                listid = '1.example.com', listemail='1@example.com')
+        self.p1.save()
+
+    def get_email(self):
+        email = create_email(self.patch)
+        del email['List-ID']
+        email['List-ID'] = '<' + self.p1.listid + '>'
+        email['Message-Id'] = self.msgid
+        return email
+
+    def _assertDelegate(self, delegate):
+        query = Patch.objects.filter(project=self.p1)
+        self.assertEquals(query.count(), 1)
+        self.assertEquals(query[0].delegate, delegate)
+
+    def testDelegate(self):
+        email = self.get_email()
+        email['X-Patchwork-Delegate'] = self.user.email
+        parse_mail(email)
+        self._assertDelegate(self.user)
+
+    def testNoDelegate(self):
+        email = self.get_email()
+        parse_mail(email)
+        self._assertDelegate(None)
+
+    def testInvalidDelegateFallsBackToNoDelegate(self):
+        email = self.get_email()
+        email['X-Patchwork-Delegate'] = self.invalid_delegate_email
+        parse_mail(email)
+        self._assertDelegate(None)
+
+    def tearDown(self):
+        self.p1.delete()
+        self.user.delete()
+
+class InitialPatchStateTest(TestCase):
+    patch_filename = '0001-add-line.patch'
+    msgid = '<1@example.com>'
+    invalid_state_name = "Nonexistent Test State"
+
+    def setUp(self):
+        self.patch = read_patch(self.patch_filename)
+        self.user = create_user()
+        self.p1 = Project(linkname = 'test-project-1', name = 'Project 1',
+                listid = '1.example.com', listemail='1@example.com')
+        self.p1.save()
+        self.default_state = get_default_initial_patch_state()
+        self.nondefault_state = State.objects.get(name="Accepted")
+
+    def get_email(self):
+        email = create_email(self.patch)
+        del email['List-ID']
+        email['List-ID'] = '<' + self.p1.listid + '>'
+        email['Message-Id'] = self.msgid
+        return email
+
+    def _assertState(self, state):
+        query = Patch.objects.filter(project=self.p1)
+        self.assertEquals(query.count(), 1)
+        self.assertEquals(query[0].state, state)
+
+    def testNonDefaultStateIsActuallyNotTheDefaultState(self):
+        self.assertNotEqual(self.default_state, self.nondefault_state)
+
+    def testExplicitNonDefaultStateRequest(self):
+        email = self.get_email()
+        email['X-Patchwork-State'] = self.nondefault_state.name
+        parse_mail(email)
+        self._assertState(self.nondefault_state)
+
+    def testExplicitDefaultStateRequest(self):
+        email = self.get_email()
+        email['X-Patchwork-State'] = self.default_state.name
+        parse_mail(email)
+        self._assertState(self.default_state)
+
+    def testImplicitDefaultStateRequest(self):
+        email = self.get_email()
+        parse_mail(email)
+        self._assertState(self.default_state)
+
+    def testInvalidTestStateDoesNotExist(self):
+        with self.assertRaises(State.DoesNotExist):
+            State.objects.get(name=self.invalid_state_name)
+
+    def testInvalidStateRequestFallsBackToDefaultState(self):
+        email = self.get_email()
+        email['X-Patchwork-State'] = self.invalid_state_name
+        parse_mail(email)
+        self._assertState(self.default_state)
+
+    def tearDown(self):
+        self.p1.delete()
+        self.user.delete()
diff --git a/apps/patchwork/tests/test_person.py b/apps/patchwork/tests/test_person.py
new file mode 100644 (file)
index 0000000..d948096
--- /dev/null
@@ -0,0 +1,55 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2013 Jeremy Kerr <jk@ozlabs.org>
+#
+# This file is part of the Patchwork package.
+#
+# Patchwork is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# Patchwork is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Patchwork; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+import unittest
+from django.test import TestCase
+from django.test.client import Client
+from patchwork.models import EmailConfirmation, Person, Bundle
+import json
+
+class SubmitterCompletionTest(TestCase):
+    def setUp(self):
+        self.people = [
+            Person(name = "Test Name", email = "test1@example.com"),
+            Person(email = "test2@example.com"),
+        ]
+        map(lambda p: p.save(), self.people)
+
+    def testNameComplete(self):
+        response = self.client.get('/submitter/', {'q': 'name'})
+        self.assertEquals(response.status_code, 200)
+        data = json.loads(response.content)
+        self.assertEquals(len(data), 1)
+        self.assertEquals(data[0]['fields']['name'], 'Test Name')
+
+    def testEmailComplete(self):
+        response = self.client.get('/submitter/', {'q': 'test2'})
+        self.assertEquals(response.status_code, 200)
+        data = json.loads(response.content)
+        self.assertEquals(len(data), 1)
+        self.assertEquals(data[0]['fields']['email'], 'test2@example.com')
+
+    def testCompleteLimit(self):
+        for i in range(3,10):
+            person = Person(email = 'test%d@example.com' % i)
+            person.save()
+        response = self.client.get('/submitter/', {'q': 'test', 'l': 5})
+        self.assertEquals(response.status_code, 200)
+        data = json.loads(response.content)
+        self.assertEquals(len(data), 5)
diff --git a/apps/patchwork/tests/test_registration.py b/apps/patchwork/tests/test_registration.py
new file mode 100644 (file)
index 0000000..845b60b
--- /dev/null
@@ -0,0 +1,210 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2010 Jeremy Kerr <jk@ozlabs.org>
+#
+# This file is part of the Patchwork package.
+#
+# Patchwork is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# Patchwork is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Patchwork; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+import unittest
+from django.test import TestCase
+from django.test.client import Client
+from django.core import mail
+from django.core.urlresolvers import reverse
+from django.contrib.auth.models import User
+from patchwork.models import EmailConfirmation, Person
+from patchwork.tests.utils import create_user
+
+def _confirmation_url(conf):
+    return reverse('patchwork.views.confirm', kwargs = {'key': conf.key})
+
+class TestUser(object):
+    firstname = 'Test'
+    lastname = 'User'
+    username = 'testuser'
+    email = 'test@example.com'
+    password = 'foobar'
+
+class RegistrationTest(TestCase):
+    def setUp(self):
+        self.user = TestUser()
+        self.client = Client()
+        self.default_data = {'username': self.user.username,
+                             'first_name': self.user.firstname,
+                             'last_name': self.user.lastname,
+                             'email': self.user.email,
+                             'password': self.user.password}
+        self.required_error = 'This field is required.'
+        self.invalid_error = 'Enter a valid value.'
+
+    def testRegistrationForm(self):
+        response = self.client.get('/register/')
+        self.assertEquals(response.status_code, 200)
+        self.assertTemplateUsed(response, 'patchwork/registration_form.html')
+
+    def testBlankFields(self):
+        for field in ['username', 'email', 'password']:
+            data = self.default_data.copy()
+            del data[field]
+            response = self.client.post('/register/', data)
+            self.assertEquals(response.status_code, 200)
+            self.assertFormError(response, 'form', field, self.required_error)
+
+    def testInvalidUsername(self):
+        data = self.default_data.copy()
+        data['username'] = 'invalid user'
+        response = self.client.post('/register/', data)
+        self.assertEquals(response.status_code, 200)
+        self.assertFormError(response, 'form', 'username', self.invalid_error)
+
+    def testExistingUsername(self):
+        user = create_user()
+        data = self.default_data.copy()
+        data['username'] = user.username
+        response = self.client.post('/register/', data)
+        self.assertEquals(response.status_code, 200)
+        self.assertFormError(response, 'form', 'username',
+                'This username is already taken. Please choose another.')
+
+    def testExistingEmail(self):
+        user = create_user()
+        data = self.default_data.copy()
+        data['email'] = user.email
+        response = self.client.post('/register/', data)
+        self.assertEquals(response.status_code, 200)
+        self.assertFormError(response, 'form', 'email',
+                'This email address is already in use ' + \
+                'for the account "%s".\n' % user.username)
+
+    def testValidRegistration(self):
+        response = self.client.post('/register/', self.default_data)
+        self.assertEquals(response.status_code, 200)
+        self.assertContains(response, 'confirmation email has been sent')
+
+        # check for presence of an inactive user object
+        users = User.objects.filter(username = self.user.username)
+        self.assertEquals(users.count(), 1)
+        user = users[0]
+        self.assertEquals(user.username, self.user.username)
+        self.assertEquals(user.email, self.user.email)
+        self.assertEquals(user.is_active, False)
+
+        # check for confirmation object
+        confs = EmailConfirmation.objects.filter(user = user,
+                                                 type = 'registration')
+        self.assertEquals(len(confs), 1)
+        conf = confs[0]
+        self.assertEquals(conf.email, self.user.email)
+
+        # check for a sent mail
+        self.assertEquals(len(mail.outbox), 1)
+        msg = mail.outbox[0]
+        self.assertEquals(msg.subject, 'Patchwork account confirmation')
+        self.assertTrue(self.user.email in msg.to)
+        self.assertTrue(_confirmation_url(conf) in msg.body)
+
+        # ...and that the URL is valid
+        response = self.client.get(_confirmation_url(conf))
+        self.assertEquals(response.status_code, 200)
+
+class RegistrationConfirmationTest(TestCase):
+
+    def setUp(self):
+        self.user = TestUser()
+        self.default_data = {'username': self.user.username,
+                             'first_name': self.user.firstname,
+                             'last_name': self.user.lastname,
+                             'email': self.user.email,
+                             'password': self.user.password}
+
+    def testRegistrationConfirmation(self):
+        self.assertEqual(EmailConfirmation.objects.count(), 0)
+        response = self.client.post('/register/', self.default_data)
+        self.assertEquals(response.status_code, 200)
+        self.assertContains(response, 'confirmation email has been sent')
+
+        self.assertEqual(EmailConfirmation.objects.count(), 1)
+        conf = EmailConfirmation.objects.filter()[0]
+        self.assertFalse(conf.user.is_active)
+        self.assertTrue(conf.active)
+
+        response = self.client.get(_confirmation_url(conf))
+        self.assertEquals(response.status_code, 200)
+        self.assertTemplateUsed(response, 'patchwork/registration-confirm.html')
+
+        conf = EmailConfirmation.objects.get(pk = conf.pk)
+        self.assertTrue(conf.user.is_active)
+        self.assertFalse(conf.active)
+
+    def testRegistrationNewPersonSetup(self):
+        """ Check that the person object created after registration has the
+            correct details """
+
+        # register
+        self.assertEqual(EmailConfirmation.objects.count(), 0)
+        response = self.client.post('/register/', self.default_data)
+        self.assertEquals(response.status_code, 200)
+        self.assertFalse(Person.objects.exists())
+
+        # confirm
+        conf = EmailConfirmation.objects.filter()[0]
+        response = self.client.get(_confirmation_url(conf))
+        self.assertEquals(response.status_code, 200)
+
+        qs = Person.objects.filter(email = self.user.email)
+        self.assertTrue(qs.exists())
+        person = Person.objects.get(email = self.user.email)
+
+        self.assertEquals(person.name,
+                    self.user.firstname + ' ' + self.user.lastname)
+
+    def testRegistrationExistingPersonSetup(self):
+        """ Check that the person object created after registration has the
+            correct details """
+
+        fullname = self.user.firstname + ' '  + self.user.lastname
+        person = Person(name = fullname, email = self.user.email)
+        person.save()
+
+        # register
+        self.assertEqual(EmailConfirmation.objects.count(), 0)
+        response = self.client.post('/register/', self.default_data)
+        self.assertEquals(response.status_code, 200)
+
+        # confirm
+        conf = EmailConfirmation.objects.filter()[0]
+        response = self.client.get(_confirmation_url(conf))
+        self.assertEquals(response.status_code, 200)
+
+        person = Person.objects.get(email = self.user.email)
+
+        self.assertEquals(person.name, fullname)
+
+    def testRegistrationExistingPersonUnmodified(self):
+        """ Check that an unconfirmed registration can't modify an existing
+            Person object"""
+
+        fullname = self.user.firstname + ' '  + self.user.lastname
+        person = Person(name = fullname, email = self.user.email)
+        person.save()
+
+        # register
+        data = self.default_data.copy()
+        data['first_name'] = 'invalid'
+        data['last_name'] = 'invalid'
+        self.assertEquals(data['email'], person.email)
+        response = self.client.post('/register/', data)
+        self.assertEquals(response.status_code, 200)
+
+        self.assertEquals(Person.objects.get(pk = person.pk).name, fullname)
diff --git a/apps/patchwork/tests/test_updates.py b/apps/patchwork/tests/test_updates.py
new file mode 100644 (file)
index 0000000..177ee78
--- /dev/null
@@ -0,0 +1,118 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2010 Jeremy Kerr <jk@ozlabs.org>
+#
+# This file is part of the Patchwork package.
+#
+# Patchwork is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# Patchwork is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Patchwork; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+from django.test import TestCase
+from django.core.urlresolvers import reverse
+from patchwork.models import Patch, Person, State
+from patchwork.tests.utils import defaults, create_maintainer
+
+class MultipleUpdateTest(TestCase):
+    def setUp(self):
+        defaults.project.save()
+        self.user = create_maintainer(defaults.project)
+        self.client.login(username = self.user.username,
+                password = self.user.username)
+        self.properties_form_id = 'patchform-properties'
+        self.url = reverse(
+            'patchwork.views.patch.list', args = [defaults.project.linkname])
+        self.base_data = {
+            'action': 'Update', 'project': str(defaults.project.id),
+            'form': 'patchlistform', 'archived': '*', 'delegate': '*',
+            'state': '*'}
+        self.patches = []
+        for name in ['patch one', 'patch two', 'patch three']:
+            patch = Patch(project = defaults.project, msgid = name,
+                            name = name, content = '',
+                            submitter = Person.objects.get(user = self.user))
+            patch.save()
+            self.patches.append(patch)
+
+    def _selectAllPatches(self, data):
+        for patch in self.patches:
+            data['patch_id:%d' % patch.id] = 'checked'
+
+    def testArchivingPatches(self):
+        data = self.base_data.copy()
+        data.update({'archived': 'True'})
+        self._selectAllPatches(data)
+        response = self.client.post(self.url, data)
+        self.assertContains(response, 'No patches to display',
+                            status_code = 200)
+        for patch in [Patch.objects.get(pk = p.pk) for p in self.patches]:
+            self.assertTrue(patch.archived)
+
+    def testUnArchivingPatches(self):
+        # Start with one patch archived and the remaining ones unarchived.
+        self.patches[0].archived = True
+        self.patches[0].save()
+        data = self.base_data.copy()
+        data.update({'archived': 'False'})
+        self._selectAllPatches(data)
+        response = self.client.post(self.url, data)
+        self.assertContains(response, self.properties_form_id,
+                            status_code = 200)
+        for patch in [Patch.objects.get(pk = p.pk) for p in self.patches]:
+            self.assertFalse(patch.archived)
+
+    def _testStateChange(self, state):
+        data = self.base_data.copy()
+        data.update({'state': str(state)})
+        self._selectAllPatches(data)
+        response = self.client.post(self.url, data)
+        self.assertContains(response, self.properties_form_id,
+                            status_code = 200)
+        return response
+
+    def testStateChangeValid(self):
+        states = [patch.state.pk for patch in self.patches]
+        state = State.objects.exclude(pk__in = states)[0]
+        self._testStateChange(state.pk)
+        for p in self.patches:
+            self.assertEquals(Patch.objects.get(pk = p.pk).state, state)
+
+    def testStateChangeInvalid(self):
+        state = max(State.objects.all().values_list('id', flat = True)) + 1
+        orig_states = [patch.state for patch in self.patches]
+        response = self._testStateChange(state)
+        self.assertEquals( \
+                [Patch.objects.get(pk = p.pk).state for p in self.patches],
+                orig_states)
+        self.assertFormError(response, 'patchform', 'state',
+                    'Select a valid choice. That choice is not one ' + \
+                        'of the available choices.')
+
+    def _testDelegateChange(self, delegate_str):
+        data = self.base_data.copy()
+        data.update({'delegate': delegate_str})
+        self._selectAllPatches(data)
+        response = self.client.post(self.url, data)
+        self.assertContains(response, self.properties_form_id,
+                            status_code=200)
+        return response
+
+    def testDelegateChangeValid(self):
+        delegate = create_maintainer(defaults.project)
+        response = self._testDelegateChange(str(delegate.pk))
+        for p in self.patches:
+            self.assertEquals(Patch.objects.get(pk = p.pk).delegate, delegate)
+
+    def testDelegateClear(self):
+        response = self._testDelegateChange('')
+        for p in self.patches:
+            self.assertEquals(Patch.objects.get(pk = p.pk).delegate, None)
diff --git a/apps/patchwork/tests/test_user.py b/apps/patchwork/tests/test_user.py
new file mode 100644 (file)
index 0000000..08b9fa5
--- /dev/null
@@ -0,0 +1,157 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2010 Jeremy Kerr <jk@ozlabs.org>
+#
+# This file is part of the Patchwork package.
+#
+# Patchwork is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# Patchwork is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Patchwork; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+import unittest
+from django.test import TestCase
+from django.test.client import Client
+from django.core import mail
+from django.core.urlresolvers import reverse
+from django.conf import settings
+from django.contrib.auth.models import User
+from patchwork.models import EmailConfirmation, Person, Bundle
+from patchwork.tests.utils import defaults, error_strings
+
+def _confirmation_url(conf):
+    return reverse('patchwork.views.confirm', kwargs = {'key': conf.key})
+
+class TestUser(object):
+    username = 'testuser'
+    email = 'test@example.com'
+    secondary_email = 'test2@example.com'
+    password = None
+
+    def __init__(self):
+        self.password = User.objects.make_random_password()
+        self.user = User.objects.create_user(self.username,
+                            self.email, self.password)
+
+class UserPersonRequestTest(TestCase):
+    def setUp(self):
+        self.user = TestUser()
+        self.client.login(username = self.user.username,
+                          password = self.user.password)
+        EmailConfirmation.objects.all().delete()
+
+    def testUserPersonRequestForm(self):
+        response = self.client.get('/user/link/')
+        self.assertEquals(response.status_code, 200)
+        self.assertTrue(response.context['linkform'])
+
+    def testUserPersonRequestEmpty(self):
+        response = self.client.post('/user/link/', {'email': ''})
+        self.assertEquals(response.status_code, 200)
+        self.assertTrue(response.context['linkform'])
+        self.assertFormError(response, 'linkform', 'email',
+                'This field is required.')
+
+    def testUserPersonRequestInvalid(self):
+        response = self.client.post('/user/link/', {'email': 'foo'})
+        self.assertEquals(response.status_code, 200)
+        self.assertTrue(response.context['linkform'])
+        self.assertFormError(response, 'linkform', 'email',
+                                error_strings['email'])
+
+    def testUserPersonRequestValid(self):
+        response = self.client.post('/user/link/',
+                                {'email': self.user.secondary_email})
+        self.assertEquals(response.status_code, 200)
+        self.assertTrue(response.context['confirmation'])
+
+        # check that we have a confirmation saved
+        self.assertEquals(EmailConfirmation.objects.count(), 1)
+        conf = EmailConfirmation.objects.all()[0]
+        self.assertEquals(conf.user, self.user.user)
+        self.assertEquals(conf.email, self.user.secondary_email)
+        self.assertEquals(conf.type, 'userperson')
+
+        # check that an email has gone out...
+        self.assertEquals(len(mail.outbox), 1)
+        msg = mail.outbox[0]
+        self.assertEquals(msg.subject, 'Patchwork email address confirmation')
+        self.assertTrue(self.user.secondary_email in msg.to)
+        self.assertTrue(_confirmation_url(conf) in msg.body)
+
+        # ...and that the URL is valid
+        response = self.client.get(_confirmation_url(conf))
+        self.assertEquals(response.status_code, 200)
+        self.assertTemplateUsed(response, 'patchwork/user-link-confirm.html')
+
+class UserPersonConfirmTest(TestCase):
+    def setUp(self):
+        EmailConfirmation.objects.all().delete()
+        Person.objects.all().delete()
+        self.user = TestUser()
+        self.client.login(username = self.user.username,
+                          password = self.user.password)
+        self.conf = EmailConfirmation(type = 'userperson',
+                                      email = self.user.secondary_email,
+                                      user = self.user.user)
+        self.conf.save()
+
+    def testUserPersonConfirm(self):
+        self.assertEquals(Person.objects.count(), 0)
+        response = self.client.get(_confirmation_url(self.conf))
+        self.assertEquals(response.status_code, 200)
+
+        # check that the Person object has been created and linked
+        self.assertEquals(Person.objects.count(), 1)
+        person = Person.objects.get(email = self.user.secondary_email)
+        self.assertEquals(person.email, self.user.secondary_email)
+        self.assertEquals(person.user, self.user.user)
+
+        # check that the confirmation has been marked as inactive. We
+        # need to reload the confirmation to check this.
+        conf = EmailConfirmation.objects.get(pk = self.conf.pk)
+        self.assertEquals(conf.active, False)
+
+class UserLoginRedirectTest(TestCase):
+    
+    def testUserLoginRedirect(self):
+        url = '/user/'
+        response = self.client.get(url)
+        self.assertRedirects(response, settings.LOGIN_URL + '?next=' + url)
+
+class UserProfileTest(TestCase):
+
+    def setUp(self):
+        self.user = TestUser()
+        self.client.login(username = self.user.username,
+                          password = self.user.password)
+
+    def testUserProfile(self):
+        response = self.client.get('/user/')
+        self.assertContains(response, 'User Profile: %s' % self.user.username)
+        self.assertContains(response, 'User Profile: %s' % self.user.username)
+
+    def testUserProfileNoBundles(self):
+        response = self.client.get('/user/')
+        self.assertContains(response, 'You have no bundles')
+
+    def testUserProfileBundles(self):
+        project = defaults.project
+        project.save()
+
+        bundle = Bundle(project = project, name = 'test-1',
+                        owner = self.user.user)
+        bundle.save()
+
+        response = self.client.get('/user/')
+
+        self.assertContains(response, 'You have the following bundle')
+        self.assertContains(response, bundle.get_absolute_url())
diff --git a/apps/patchwork/tests/updates.py b/apps/patchwork/tests/updates.py
deleted file mode 100644 (file)
index 177ee78..0000000
+++ /dev/null
@@ -1,118 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2010 Jeremy Kerr <jk@ozlabs.org>
-#
-# This file is part of the Patchwork package.
-#
-# Patchwork is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# Patchwork is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with Patchwork; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-from django.test import TestCase
-from django.core.urlresolvers import reverse
-from patchwork.models import Patch, Person, State
-from patchwork.tests.utils import defaults, create_maintainer
-
-class MultipleUpdateTest(TestCase):
-    def setUp(self):
-        defaults.project.save()
-        self.user = create_maintainer(defaults.project)
-        self.client.login(username = self.user.username,
-                password = self.user.username)
-        self.properties_form_id = 'patchform-properties'
-        self.url = reverse(
-            'patchwork.views.patch.list', args = [defaults.project.linkname])
-        self.base_data = {
-            'action': 'Update', 'project': str(defaults.project.id),
-            'form': 'patchlistform', 'archived': '*', 'delegate': '*',
-            'state': '*'}
-        self.patches = []
-        for name in ['patch one', 'patch two', 'patch three']:
-            patch = Patch(project = defaults.project, msgid = name,
-                            name = name, content = '',
-                            submitter = Person.objects.get(user = self.user))
-            patch.save()
-            self.patches.append(patch)
-
-    def _selectAllPatches(self, data):
-        for patch in self.patches:
-            data['patch_id:%d' % patch.id] = 'checked'
-
-    def testArchivingPatches(self):
-        data = self.base_data.copy()
-        data.update({'archived': 'True'})
-        self._selectAllPatches(data)
-        response = self.client.post(self.url, data)
-        self.assertContains(response, 'No patches to display',
-                            status_code = 200)
-        for patch in [Patch.objects.get(pk = p.pk) for p in self.patches]:
-            self.assertTrue(patch.archived)
-
-    def testUnArchivingPatches(self):
-        # Start with one patch archived and the remaining ones unarchived.
-        self.patches[0].archived = True
-        self.patches[0].save()
-        data = self.base_data.copy()
-        data.update({'archived': 'False'})
-        self._selectAllPatches(data)
-        response = self.client.post(self.url, data)
-        self.assertContains(response, self.properties_form_id,
-                            status_code = 200)
-        for patch in [Patch.objects.get(pk = p.pk) for p in self.patches]:
-            self.assertFalse(patch.archived)
-
-    def _testStateChange(self, state):
-        data = self.base_data.copy()
-        data.update({'state': str(state)})
-        self._selectAllPatches(data)
-        response = self.client.post(self.url, data)
-        self.assertContains(response, self.properties_form_id,
-                            status_code = 200)
-        return response
-
-    def testStateChangeValid(self):
-        states = [patch.state.pk for patch in self.patches]
-        state = State.objects.exclude(pk__in = states)[0]
-        self._testStateChange(state.pk)
-        for p in self.patches:
-            self.assertEquals(Patch.objects.get(pk = p.pk).state, state)
-
-    def testStateChangeInvalid(self):
-        state = max(State.objects.all().values_list('id', flat = True)) + 1
-        orig_states = [patch.state for patch in self.patches]
-        response = self._testStateChange(state)
-        self.assertEquals( \
-                [Patch.objects.get(pk = p.pk).state for p in self.patches],
-                orig_states)
-        self.assertFormError(response, 'patchform', 'state',
-                    'Select a valid choice. That choice is not one ' + \
-                        'of the available choices.')
-
-    def _testDelegateChange(self, delegate_str):
-        data = self.base_data.copy()
-        data.update({'delegate': delegate_str})
-        self._selectAllPatches(data)
-        response = self.client.post(self.url, data)
-        self.assertContains(response, self.properties_form_id,
-                            status_code=200)
-        return response
-
-    def testDelegateChangeValid(self):
-        delegate = create_maintainer(defaults.project)
-        response = self._testDelegateChange(str(delegate.pk))
-        for p in self.patches:
-            self.assertEquals(Patch.objects.get(pk = p.pk).delegate, delegate)
-
-    def testDelegateClear(self):
-        response = self._testDelegateChange('')
-        for p in self.patches:
-            self.assertEquals(Patch.objects.get(pk = p.pk).delegate, None)
diff --git a/apps/patchwork/tests/user.py b/apps/patchwork/tests/user.py
deleted file mode 100644 (file)
index 08b9fa5..0000000
+++ /dev/null
@@ -1,157 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2010 Jeremy Kerr <jk@ozlabs.org>
-#
-# This file is part of the Patchwork package.
-#
-# Patchwork is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# Patchwork is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with Patchwork; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-import unittest
-from django.test import TestCase
-from django.test.client import Client
-from django.core import mail
-from django.core.urlresolvers import reverse
-from django.conf import settings
-from django.contrib.auth.models import User
-from patchwork.models import EmailConfirmation, Person, Bundle
-from patchwork.tests.utils import defaults, error_strings
-
-def _confirmation_url(conf):
-    return reverse('patchwork.views.confirm', kwargs = {'key': conf.key})
-
-class TestUser(object):
-    username = 'testuser'
-    email = 'test@example.com'
-    secondary_email = 'test2@example.com'
-    password = None
-
-    def __init__(self):
-        self.password = User.objects.make_random_password()
-        self.user = User.objects.create_user(self.username,
-                            self.email, self.password)
-
-class UserPersonRequestTest(TestCase):
-    def setUp(self):
-        self.user = TestUser()
-        self.client.login(username = self.user.username,
-                          password = self.user.password)
-        EmailConfirmation.objects.all().delete()
-
-    def testUserPersonRequestForm(self):
-        response = self.client.get('/user/link/')
-        self.assertEquals(response.status_code, 200)
-        self.assertTrue(response.context['linkform'])
-
-    def testUserPersonRequestEmpty(self):
-        response = self.client.post('/user/link/', {'email': ''})
-        self.assertEquals(response.status_code, 200)
-        self.assertTrue(response.context['linkform'])
-        self.assertFormError(response, 'linkform', 'email',
-                'This field is required.')
-
-    def testUserPersonRequestInvalid(self):
-        response = self.client.post('/user/link/', {'email': 'foo'})
-        self.assertEquals(response.status_code, 200)
-        self.assertTrue(response.context['linkform'])
-        self.assertFormError(response, 'linkform', 'email',
-                                error_strings['email'])
-
-    def testUserPersonRequestValid(self):
-        response = self.client.post('/user/link/',
-                                {'email': self.user.secondary_email})
-        self.assertEquals(response.status_code, 200)
-        self.assertTrue(response.context['confirmation'])
-
-        # check that we have a confirmation saved
-        self.assertEquals(EmailConfirmation.objects.count(), 1)
-        conf = EmailConfirmation.objects.all()[0]
-        self.assertEquals(conf.user, self.user.user)
-        self.assertEquals(conf.email, self.user.secondary_email)
-        self.assertEquals(conf.type, 'userperson')
-
-        # check that an email has gone out...
-        self.assertEquals(len(mail.outbox), 1)
-        msg = mail.outbox[0]
-        self.assertEquals(msg.subject, 'Patchwork email address confirmation')
-        self.assertTrue(self.user.secondary_email in msg.to)
-        self.assertTrue(_confirmation_url(conf) in msg.body)
-
-        # ...and that the URL is valid
-        response = self.client.get(_confirmation_url(conf))
-        self.assertEquals(response.status_code, 200)
-        self.assertTemplateUsed(response, 'patchwork/user-link-confirm.html')
-
-class UserPersonConfirmTest(TestCase):
-    def setUp(self):
-        EmailConfirmation.objects.all().delete()
-        Person.objects.all().delete()
-        self.user = TestUser()
-        self.client.login(username = self.user.username,
-                          password = self.user.password)
-        self.conf = EmailConfirmation(type = 'userperson',
-                                      email = self.user.secondary_email,
-                                      user = self.user.user)
-        self.conf.save()
-
-    def testUserPersonConfirm(self):
-        self.assertEquals(Person.objects.count(), 0)
-        response = self.client.get(_confirmation_url(self.conf))
-        self.assertEquals(response.status_code, 200)
-
-        # check that the Person object has been created and linked
-        self.assertEquals(Person.objects.count(), 1)
-        person = Person.objects.get(email = self.user.secondary_email)
-        self.assertEquals(person.email, self.user.secondary_email)
-        self.assertEquals(person.user, self.user.user)
-
-        # check that the confirmation has been marked as inactive. We
-        # need to reload the confirmation to check this.
-        conf = EmailConfirmation.objects.get(pk = self.conf.pk)
-        self.assertEquals(conf.active, False)
-
-class UserLoginRedirectTest(TestCase):
-    
-    def testUserLoginRedirect(self):
-        url = '/user/'
-        response = self.client.get(url)
-        self.assertRedirects(response, settings.LOGIN_URL + '?next=' + url)
-
-class UserProfileTest(TestCase):
-
-    def setUp(self):
-        self.user = TestUser()
-        self.client.login(username = self.user.username,
-                          password = self.user.password)
-
-    def testUserProfile(self):
-        response = self.client.get('/user/')
-        self.assertContains(response, 'User Profile: %s' % self.user.username)
-        self.assertContains(response, 'User Profile: %s' % self.user.username)
-
-    def testUserProfileNoBundles(self):
-        response = self.client.get('/user/')
-        self.assertContains(response, 'You have no bundles')
-
-    def testUserProfileBundles(self):
-        project = defaults.project
-        project.save()
-
-        bundle = Bundle(project = project, name = 'test-1',
-                        owner = self.user.user)
-        bundle.save()
-
-        response = self.client.get('/user/')
-
-        self.assertContains(response, 'You have the following bundle')
-        self.assertContains(response, bundle.get_absolute_url())