]> git.ipfire.org Git - thirdparty/patchwork.git/commitdiff
tests: Move view tests to their own directory
authorStephen Finucane <stephen@that.guru>
Sat, 6 Mar 2021 15:12:00 +0000 (15:12 +0000)
committerStephen Finucane <stephen@that.guru>
Sun, 7 Mar 2021 18:51:34 +0000 (18:51 +0000)
This lets our unit tests mirror the main code structure and highlights
gaps in coverage.

Signed-off-by: Stephen Finucane <stephen@that.guru>
18 files changed:
patchwork/tests/test_confirm.py [deleted file]
patchwork/tests/test_detail.py [deleted file]
patchwork/tests/test_encodings.py [deleted file]
patchwork/tests/test_filters.py [deleted file]
patchwork/tests/test_list.py [deleted file]
patchwork/tests/test_registration.py [deleted file]
patchwork/tests/test_signals.py [moved from patchwork/tests/test_events.py with 100% similarity]
patchwork/tests/test_updates.py [deleted file]
patchwork/tests/views/__init__.py [new file with mode: 0644]
patchwork/tests/views/test_about.py [moved from patchwork/tests/test_about.py with 100% similarity]
patchwork/tests/views/test_api.py [moved from patchwork/tests/test_completion.py with 100% similarity]
patchwork/tests/views/test_bundles.py [moved from patchwork/tests/test_bundles.py with 100% similarity]
patchwork/tests/views/test_cover.py [new file with mode: 0644]
patchwork/tests/views/test_mail.py [moved from patchwork/tests/test_mail_settings.py with 100% similarity]
patchwork/tests/views/test_patch.py [new file with mode: 0644]
patchwork/tests/views/test_projects.py [moved from patchwork/tests/test_projects.py with 100% similarity]
patchwork/tests/views/test_user.py [moved from patchwork/tests/test_user.py with 50% similarity]
patchwork/tests/views/test_utils.py [moved from patchwork/tests/test_mboxviews.py with 100% similarity]

diff --git a/patchwork/tests/test_confirm.py b/patchwork/tests/test_confirm.py
deleted file mode 100644 (file)
index 213e0f4..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2011 Jeremy Kerr <jk@ozlabs.org>
-#
-# SPDX-License-Identifier: GPL-2.0-or-later
-
-from django.test import TestCase
-from django.urls import reverse
-
-from patchwork.models import EmailConfirmation
-from patchwork.tests.utils import create_user
-
-
-def _confirmation_url(conf):
-    return reverse('confirm', kwargs={'key': conf.key})
-
-
-def _generate_secondary_email(user):
-    return 'secondary_%d@example.com' % user.id
-
-
-class InvalidConfirmationTest(TestCase):
-
-    def setUp(self):
-        self.user = create_user()
-        self.secondary_email = _generate_secondary_email(self.user)
-
-        self.conf = EmailConfirmation(type='userperson',
-                                      email=self.secondary_email,
-                                      user=self.user)
-        self.conf.save()
-
-    def test_inactive_confirmation(self):
-        self.conf.active = False
-        self.conf.save()
-        response = self.client.get(_confirmation_url(self.conf))
-        self.assertEqual(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 test_expired_confirmation(self):
-        self.conf.date -= self.conf.validity
-        self.conf.save()
-        response = self.client.get(_confirmation_url(self.conf))
-        self.assertEqual(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/patchwork/tests/test_detail.py b/patchwork/tests/test_detail.py
deleted file mode 100644 (file)
index 5add40f..0000000
+++ /dev/null
@@ -1,229 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2016 Intel Corporation
-#
-# SPDX-License-Identifier: GPL-2.0-or-later
-
-from datetime import datetime as dt
-from datetime import timedelta
-
-from django.test import TestCase
-from django.urls import reverse
-
-from patchwork.models import Check
-from patchwork.tests.utils import create_check
-from patchwork.tests.utils import create_cover
-from patchwork.tests.utils import create_cover_comment
-from patchwork.tests.utils import create_patch
-from patchwork.tests.utils import create_patch_comment
-from patchwork.tests.utils import create_project
-from patchwork.tests.utils import create_user
-
-
-class CoverViewTest(TestCase):
-
-    def test_redirect(self):
-        patch = create_patch()
-
-        requested_url = reverse('cover-detail',
-                                kwargs={'project_id': patch.project.linkname,
-                                        'msgid': patch.url_msgid})
-        redirect_url = reverse('patch-detail',
-                               kwargs={'project_id': patch.project.linkname,
-                                       'msgid': patch.url_msgid})
-
-        response = self.client.get(requested_url)
-        self.assertRedirects(response, redirect_url)
-
-    def test_old_detail_url(self):
-        cover = create_cover()
-
-        requested_url = reverse('cover-id-redirect',
-                                kwargs={'cover_id': cover.id})
-        redirect_url = reverse('cover-detail',
-                               kwargs={'project_id': cover.project.linkname,
-                                       'msgid': cover.url_msgid})
-
-        response = self.client.get(requested_url)
-        self.assertRedirects(response, redirect_url)
-
-    def test_old_mbox_url(self):
-        cover = create_cover()
-
-        requested_url = reverse('cover-mbox-redirect',
-                                kwargs={'cover_id': cover.id})
-        redirect_url = reverse('cover-mbox',
-                               kwargs={'project_id': cover.project.linkname,
-                                       'msgid': cover.url_msgid})
-
-        response = self.client.get(requested_url)
-        self.assertRedirects(response, redirect_url)
-
-    def test_invalid_project_id(self):
-        requested_url = reverse(
-            'cover-detail',
-            kwargs={'project_id': 'foo', 'msgid': 'bar'},
-        )
-        response = self.client.get(requested_url)
-        self.assertEqual(response.status_code, 404)
-
-    def test_invalid_cover_id(self):
-        project = create_project()
-        requested_url = reverse(
-            'cover-detail',
-            kwargs={'project_id': project.linkname, 'msgid': 'foo'},
-        )
-        response = self.client.get(requested_url)
-        self.assertEqual(response.status_code, 404)
-
-
-class PatchViewTest(TestCase):
-
-    def test_redirect(self):
-        cover = create_cover()
-
-        requested_url = reverse('patch-detail',
-                                kwargs={'project_id': cover.project.linkname,
-                                        'msgid': cover.url_msgid})
-        redirect_url = reverse('cover-detail',
-                               kwargs={'project_id': cover.project.linkname,
-                                       'msgid': cover.url_msgid})
-
-        response = self.client.get(requested_url)
-        self.assertRedirects(response, redirect_url)
-
-    def test_old_detail_url(self):
-        patch = create_patch()
-
-        requested_url = reverse('patch-id-redirect',
-                                kwargs={'patch_id': patch.id})
-        redirect_url = reverse('patch-detail',
-                               kwargs={'project_id': patch.project.linkname,
-                                       'msgid': patch.url_msgid})
-
-        response = self.client.get(requested_url)
-        self.assertRedirects(response, redirect_url)
-
-    def test_old_mbox_url(self):
-        patch = create_patch()
-
-        requested_url = reverse('patch-mbox-redirect',
-                                kwargs={'patch_id': patch.id})
-        redirect_url = reverse('patch-mbox',
-                               kwargs={'project_id': patch.project.linkname,
-                                       'msgid': patch.url_msgid})
-
-        response = self.client.get(requested_url)
-        self.assertRedirects(response, redirect_url)
-
-    def test_old_raw_url(self):
-        patch = create_patch()
-
-        requested_url = reverse('patch-raw-redirect',
-                                kwargs={'patch_id': patch.id})
-        redirect_url = reverse('patch-raw',
-                               kwargs={'project_id': patch.project.linkname,
-                                       'msgid': patch.url_msgid})
-
-        response = self.client.get(requested_url)
-        self.assertRedirects(response, redirect_url)
-
-    def test_escaping(self):
-        # Warning: this test doesn't guarantee anything - it only tests some
-        # fields
-        unescaped_string = 'blah<b>TEST</b>blah'
-        patch = create_patch()
-        patch.diff = unescaped_string
-        patch.commit_ref = unescaped_string
-        patch.pull_url = unescaped_string
-        patch.name = unescaped_string
-        patch.msgid = '<' + unescaped_string + '>'
-        patch.headers = unescaped_string
-        patch.content = unescaped_string
-        patch.save()
-        requested_url = reverse('patch-detail',
-                                kwargs={'project_id': patch.project.linkname,
-                                        'msgid': patch.url_msgid})
-        response = self.client.get(requested_url)
-        self.assertNotIn('<b>TEST</b>'.encode('utf-8'), response.content)
-
-    def test_invalid_project_id(self):
-        requested_url = reverse(
-            'patch-detail',
-            kwargs={'project_id': 'foo', 'msgid': 'bar'},
-        )
-        response = self.client.get(requested_url)
-        self.assertEqual(response.status_code, 404)
-
-    def test_invalid_patch_id(self):
-        project = create_project()
-        requested_url = reverse(
-            'patch-detail',
-            kwargs={'project_id': project.linkname, 'msgid': 'foo'},
-        )
-        response = self.client.get(requested_url)
-        self.assertEqual(response.status_code, 404)
-
-    def test_patch_with_checks(self):
-        user = create_user()
-        patch = create_patch()
-        check_a = create_check(
-            patch=patch, user=user, context='foo', state=Check.STATE_FAIL,
-            date=(dt.utcnow() - timedelta(days=1)))
-        create_check(
-            patch=patch, user=user, context='foo', state=Check.STATE_SUCCESS)
-        check_b = create_check(
-            patch=patch, user=user, context='bar', state=Check.STATE_PENDING)
-        requested_url = reverse(
-            'patch-detail',
-            kwargs={
-                'project_id': patch.project.linkname,
-                'msgid': patch.url_msgid,
-            },
-        )
-        response = self.client.get(requested_url)
-
-        # the response should contain checks
-        self.assertContains(response, '<h2>Checks</h2>')
-
-        # and it should only show the unique checks
-        self.assertEqual(
-            1, response.content.decode().count(
-                f'<td>{check_a.user}/{check_a.context}</td>'
-            ))
-        self.assertEqual(
-            1, response.content.decode().count(
-                f'<td>{check_b.user}/{check_b.context}</td>'
-            ))
-
-
-class CommentRedirectTest(TestCase):
-
-    def test_patch_redirect(self):
-        patch = create_patch()
-        comment_id = create_patch_comment(patch=patch).id
-
-        requested_url = reverse('comment-redirect',
-                                kwargs={'comment_id': comment_id})
-        redirect_url = '%s#%d' % (
-            reverse('patch-detail',
-                    kwargs={'project_id': patch.project.linkname,
-                            'msgid': patch.url_msgid}),
-            comment_id)
-
-        response = self.client.get(requested_url)
-        self.assertRedirects(response, redirect_url)
-
-    def test_cover_redirect(self):
-        cover = create_cover()
-        comment_id = create_cover_comment(cover=cover).id
-
-        requested_url = reverse('comment-redirect',
-                                kwargs={'comment_id': comment_id})
-        redirect_url = '%s#%d' % (
-            reverse('cover-detail',
-                    kwargs={'project_id': cover.project.linkname,
-                            'msgid': cover.url_msgid}),
-            comment_id)
-
-        response = self.client.get(requested_url)
-        self.assertRedirects(response, redirect_url)
diff --git a/patchwork/tests/test_encodings.py b/patchwork/tests/test_encodings.py
deleted file mode 100644 (file)
index be9e6c3..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
-#
-# SPDX-License-Identifier: GPL-2.0-or-later
-
-from django.test import TestCase
-from django.urls import reverse
-
-from patchwork.tests.utils import create_person
-from patchwork.tests.utils import create_patch
-from patchwork.tests.utils import read_patch
-
-
-class UTF8PatchViewTest(TestCase):
-
-    def setUp(self):
-        patch_content = read_patch('0002-utf-8.patch', encoding='utf-8')
-        self.patch = create_patch(diff=patch_content)
-
-    def test_patch_view(self):
-        response = self.client.get(reverse(
-            'patch-detail', args=[self.patch.project.linkname,
-                                  self.patch.url_msgid]))
-        self.assertContains(response, self.patch.name)
-
-    def test_mbox_view(self):
-        response = self.client.get(
-            reverse('patch-mbox', args=[self.patch.project.linkname,
-                                        self.patch.url_msgid]))
-        self.assertEqual(response.status_code, 200)
-        self.assertTrue(self.patch.diff in response.content.decode('utf-8'))
-
-    def test_raw_view(self):
-        response = self.client.get(reverse('patch-raw',
-                                           args=[self.patch.project.linkname,
-                                                 self.patch.url_msgid]))
-        self.assertEqual(response.status_code, 200)
-        self.assertEqual(response.content.decode('utf-8'), self.patch.diff)
-
-
-class UTF8HeaderPatchViewTest(UTF8PatchViewTest):
-
-    def setUp(self):
-        author = create_person(name=u'P\xe4tch Author')
-        patch_content = read_patch('0002-utf-8.patch', encoding='utf-8')
-        self.patch = create_patch(submitter=author, diff=patch_content)
diff --git a/patchwork/tests/test_filters.py b/patchwork/tests/test_filters.py
deleted file mode 100644 (file)
index a69c4f8..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2011 Jeremy Kerr <jk@ozlabs.org>
-#
-# SPDX-License-Identifier: GPL-2.0-or-later
-
-from django.test import TestCase
-from django.urls import reverse
-
-from patchwork.tests.utils import create_project
-
-
-class FilterQueryStringTest(TestCase):
-
-    def test_escaping(self):
-        """Validate escaping of filter fragments in a query string.
-
-        Stray ampersands should not get reflected back in the filter
-        links.
-        """
-        project = create_project()
-        url = reverse('patch-list', args=[project.linkname])
-
-        response = self.client.get(url + '?submitter=a%%26b=c')
-
-        self.assertEqual(response.status_code, 200)
-        self.assertNotContains(response, 'submitter=a&amp;b=c')
-        self.assertNotContains(response, 'submitter=a&b=c')
-
-    def test_utf8_handling(self):
-        """Validate handling of non-ascii characters."""
-        project = create_project()
-        url = reverse('patch-list', args=[project.linkname])
-
-        response = self.client.get(url + '?submitter=%%E2%%98%%83')
-
-        self.assertEqual(response.status_code, 200)
diff --git a/patchwork/tests/test_list.py b/patchwork/tests/test_list.py
deleted file mode 100644 (file)
index c11a992..0000000
+++ /dev/null
@@ -1,130 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2012 Jeremy Kerr <jk@ozlabs.org>
-#
-# SPDX-License-Identifier: GPL-2.0-or-later
-
-from datetime import datetime as dt
-import unittest
-import re
-
-from django.conf import settings
-from django.test import TestCase
-from django.urls import reverse
-
-from patchwork.models import Patch
-from patchwork.tests.utils import create_patch
-from patchwork.tests.utils import create_person
-from patchwork.tests.utils import create_project
-
-
-class EmptyPatchListTest(TestCase):
-
-    def test_empty_patch_list(self):
-        """Validates absence of table with zero patches."""
-        project = create_project()
-        url = reverse('patch-list', kwargs={'project_id': project.linkname})
-        response = self.client.get(url)
-        self.assertContains(response, 'No patches to display')
-
-
-class PatchOrderTest(TestCase):
-
-    patchmeta = [
-        ('AlCMyjOsx', 'AlxMyjOsx@nRbqkQV.wBw',
-         dt(2014, 3, 16, 13, 4, 50, 155643)),
-        ('MMZnrcDjT', 'MMmnrcDjT@qGaIfOl.tbk',
-         dt(2014, 1, 25, 13, 4, 50, 162814)),
-        ('WGirwRXgK', 'WGSrwRXgK@TriIETY.GhE',
-         dt(2014, 2, 14, 13, 4, 50, 169305)),
-        ('isjNIuiAc', 'issNIuiAc@OsEirYx.EJh',
-         dt(2014, 3, 15, 13, 4, 50, 176264)),
-        ('XkAQpYGws', 'XkFQpYGws@hzntTcm.JSE',
-         dt(2014, 1, 18, 13, 4, 50, 182493)),
-        ('uJuCPWMvi', 'uJACPWMvi@AVRBOBl.ecy',
-         dt(2014, 3, 12, 13, 4, 50, 189554)),
-        ('TyQmWtcbg', 'TylmWtcbg@DzrNeNH.JuB',
-         dt(2014, 2, 3, 13, 4, 50, 195685)),
-        ('FpvAhWRdX', 'FpKAhWRdX@agxnCAI.wFO',
-         dt(2014, 3, 15, 13, 4, 50, 201398)),
-        ('bmoYvnyWa', 'bmdYvnyWa@aeoPnlX.juy',
-         dt(2014, 3, 4, 13, 4, 50, 206800)),
-        ('CiReUQsAq', 'CiieUQsAq@DnOYRuf.TTI',
-         dt(2014, 3, 28, 13, 4, 50, 212169)),
-    ]
-
-    def setUp(self):
-        self.project = create_project()
-
-        for name, email, date in self.patchmeta:
-            person = create_person(name=name, email=email)
-            create_patch(submitter=person, project=self.project,
-                         date=date)
-
-    def _extract_patch_ids(self, response):
-        id_re = re.compile(r'<tr id="patch_row:(\d+)"')
-        ids = [int(m.group(1))
-               for m in id_re.finditer(response.content.decode())]
-
-        return ids
-
-    def _test_sequence(self, response, test_fn):
-        ids = self._extract_patch_ids(response)
-        self.assertTrue(bool(ids))
-        patches = [Patch.objects.get(id=i) for i in ids]
-        pairs = list(zip(patches, patches[1:]))
-
-        for p1, p2 in pairs:
-            test_fn(p1, p2)
-
-    def test_date_order(self):
-        url = reverse('patch-list',
-                      kwargs={'project_id': self.project.linkname})
-        response = self.client.get(url + '?order=date')
-
-        def test_fn(p1, p2):
-            self.assertLessEqual(p1.date, p2.date)
-
-        self._test_sequence(response, test_fn)
-
-    def test_date_reverse_order(self):
-        url = reverse('patch-list',
-                      kwargs={'project_id': self.project.linkname})
-        response = self.client.get(url + '?order=-date')
-
-        def test_fn(p1, p2):
-            self.assertGreaterEqual(p1.date, p2.date)
-
-        self._test_sequence(response, test_fn)
-
-    # TODO(stephenfin): Looks like this has been resolved in Django 2.1 [1]? If
-    # not, it should be possible [2]
-    #
-    # [1] https://code.djangoproject.com/ticket/30248
-    # [2] https://michaelsoolee.com/case-insensitive-sorting-sqlite/
-    @unittest.skipIf('sqlite3' in settings.DATABASES['default']['ENGINE'],
-                     'The sqlite3 backend does not support case insensitive '
-                     'ordering')
-    def test_submitter_order(self):
-        url = reverse('patch-list',
-                      kwargs={'project_id': self.project.linkname})
-        response = self.client.get(url + '?order=submitter')
-
-        def test_fn(p1, p2):
-            self.assertLessEqual(p1.submitter.name.lower(),
-                                 p2.submitter.name.lower())
-
-        self._test_sequence(response, test_fn)
-
-    @unittest.skipIf('sqlite3' in settings.DATABASES['default']['ENGINE'],
-                     'The sqlite3 backend does not support case insensitive '
-                     'ordering')
-    def test_submitter_reverse_order(self):
-        url = reverse('patch-list',
-                      kwargs={'project_id': self.project.linkname})
-        response = self.client.get(url + '?order=-submitter')
-
-        def test_fn(p1, p2):
-            self.assertGreaterEqual(p1.submitter.name.lower(),
-                                    p2.submitter.name.lower())
-
-        self._test_sequence(response, test_fn)
diff --git a/patchwork/tests/test_registration.py b/patchwork/tests/test_registration.py
deleted file mode 100644 (file)
index 20a54d5..0000000
+++ /dev/null
@@ -1,216 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2010 Jeremy Kerr <jk@ozlabs.org>
-#
-# SPDX-License-Identifier: GPL-2.0-or-later
-
-from django.contrib.auth.models import User
-from django.core import mail
-from django.test.client import Client
-from django.test import TestCase
-from django.urls import reverse
-
-from patchwork.models import EmailConfirmation
-from patchwork.models import Person
-from patchwork.tests.utils import create_user
-
-
-def _confirmation_url(conf):
-    return reverse('confirm', kwargs={'key': conf.key})
-
-
-class TestUser(object):
-    firstname = 'Test'
-    lastname = 'User'
-    fullname = ' '.join([firstname, lastname])
-    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 test_registration_form(self):
-        response = self.client.get('/register/')
-        self.assertEqual(response.status_code, 200)
-        self.assertTemplateUsed(response, 'patchwork/registration.html')
-
-    def test_blank_fields(self):
-        for field in ['username', 'email', 'password']:
-            data = self.default_data.copy()
-            del data[field]
-            response = self.client.post('/register/', data)
-            self.assertEqual(response.status_code, 200)
-            self.assertFormError(response, 'form', field, self.required_error)
-
-    def test_invalid_username(self):
-        data = self.default_data.copy()
-        data['username'] = 'invalid user'
-        response = self.client.post('/register/', data)
-        self.assertEqual(response.status_code, 200)
-        self.assertFormError(response, 'form', 'username', self.invalid_error)
-
-    def test_existing_username(self):
-        user = create_user()
-        data = self.default_data.copy()
-        data['username'] = user.username
-        response = self.client.post('/register/', data)
-        self.assertEqual(response.status_code, 200)
-        self.assertFormError(
-            response, 'form', 'username',
-            'This username is already taken. Please choose another.')
-
-    def test_existing_email(self):
-        user = create_user()
-        data = self.default_data.copy()
-        data['email'] = user.email
-        response = self.client.post('/register/', data)
-        self.assertEqual(response.status_code, 200)
-        self.assertFormError(
-            response, 'form', 'email',
-            'This email address is already in use for the account '
-            '"%s".\n' % user.username)
-
-    def test_valid_registration(self):
-        response = self.client.post('/register/', self.default_data)
-        self.assertEqual(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.assertEqual(users.count(), 1)
-        user = users[0]
-        self.assertEqual(user.username, self.user.username)
-        self.assertEqual(user.email, self.user.email)
-        self.assertEqual(user.is_active, False)
-
-        # check for confirmation object
-        confs = EmailConfirmation.objects.filter(
-            user=user, type='registration')
-        self.assertEqual(len(confs), 1)
-        conf = confs[0]
-        self.assertEqual(conf.email, self.user.email)
-
-        # check for a sent mail
-        self.assertEqual(len(mail.outbox), 1)
-        msg = mail.outbox[0]
-        self.assertEqual(msg.subject, 'Patchwork account confirmation')
-        self.assertIn(self.user.email, msg.to)
-        self.assertIn(_confirmation_url(conf), msg.body)
-
-        # ...and that the URL is valid
-        response = self.client.get(_confirmation_url(conf))
-        self.assertEqual(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 test_valid(self):
-        """Test the success path."""
-        self.assertEqual(EmailConfirmation.objects.count(), 0)
-        response = self.client.post('/register/', self.default_data)
-        self.assertEqual(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.assertEqual(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 test_new_person_setup(self):
-        """Ensure a new Person is created after account setup.
-
-        Create an account for a never before seen email. Check that a Person
-        object is created after registration and has the correct details.
-        """
-        # register
-        self.assertEqual(EmailConfirmation.objects.count(), 0)
-        response = self.client.post('/register/', self.default_data)
-        self.assertEqual(response.status_code, 200)
-        self.assertFalse(Person.objects.exists())
-
-        # confirm
-        conf = EmailConfirmation.objects.filter()[0]
-        response = self.client.get(_confirmation_url(conf))
-        self.assertEqual(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.assertEqual(person.name, self.user.fullname)
-
-    def test_existing_person_setup(self):
-        """Ensure an existing person is linked after account setup.
-
-        Create an account for a user using an email we've previously seen.
-        Check that the person object is updated after registration with the
-        correct details.
-        """
-        person = Person(name=self.user.fullname, email=self.user.email)
-        person.save()
-
-        # register
-        self.assertEqual(EmailConfirmation.objects.count(), 0)
-        response = self.client.post('/register/', self.default_data)
-        self.assertEqual(response.status_code, 200)
-
-        # confirm
-        conf = EmailConfirmation.objects.filter()[0]
-        response = self.client.get(_confirmation_url(conf))
-        self.assertEqual(response.status_code, 200)
-
-        person = Person.objects.get(email=self.user.email)
-
-        self.assertEqual(person.name, self.user.fullname)
-
-    def test_existing_person_unmodified(self):
-        """Ensure an existing person is not linked until registration is done.
-
-        Create an account for a user using an email we've previously seen but
-        don't confirm it. Check that the person object is not updated yet.
-        """
-        person = Person(name=self.user.fullname, email=self.user.email)
-        person.save()
-
-        # register
-        data = self.default_data.copy()
-        data['first_name'] = 'invalid'
-        data['last_name'] = 'invalid'
-        self.assertEqual(data['email'], person.email)
-        response = self.client.post('/register/', data)
-        self.assertEqual(response.status_code, 200)
-
-        self.assertEqual(
-            Person.objects.get(pk=person.pk).name, self.user.fullname)
diff --git a/patchwork/tests/test_updates.py b/patchwork/tests/test_updates.py
deleted file mode 100644 (file)
index a554e38..0000000
+++ /dev/null
@@ -1,125 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2010 Jeremy Kerr <jk@ozlabs.org>
-#
-# SPDX-License-Identifier: GPL-2.0-or-later
-
-from django.test import TestCase
-from django.urls import reverse
-
-from patchwork.models import Patch
-from patchwork.models import State
-from patchwork.tests.utils import create_patches
-from patchwork.tests.utils import create_project
-from patchwork.tests.utils import create_state
-from patchwork.tests.utils import create_maintainer
-
-
-class MultipleUpdateTest(TestCase):
-
-    properties_form_id = 'patchform-properties'
-
-    def setUp(self):
-        self.project = create_project()
-        self.user = create_maintainer(self.project)
-        self.patches = create_patches(3, project=self.project)
-
-        self.client.login(username=self.user.username,
-                          password=self.user.username)
-
-        self.url = reverse('patch-list', args=[self.project.linkname])
-        self.base_data = {
-            'action': 'Update',
-            'project': str(self.project.id),
-            'form': 'patchlistform',
-            'archived': '*',
-            'delegate': '*',
-            'state': '*'
-        }
-
-    def _select_all_patches(self, data):
-        for patch in self.patches:
-            data['patch_id:%d' % patch.id] = 'checked'
-
-    def test_archiving_patches(self):
-        data = self.base_data.copy()
-        data.update({'archived': 'True'})
-        self._select_all_patches(data)
-
-        response = self.client.post(self.url, data)
-
-        self.assertContains(response, 'No patches to display',
-                            status_code=200)
-        # Don't use the cached version of patches: retrieve from the DB
-        for patch in [Patch.objects.get(pk=p.pk) for p in self.patches]:
-            self.assertTrue(patch.archived)
-
-    def test_unarchiving_patches(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._select_all_patches(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 _test_state_change(self, state):
-        data = self.base_data.copy()
-        data.update({'state': str(state)})
-        self._select_all_patches(data)
-
-        response = self.client.post(self.url, data)
-
-        self.assertContains(response, self.properties_form_id,
-                            status_code=200)
-        return response
-
-    def test_state_change_valid(self):
-        state = create_state()
-
-        self._test_state_change(state.pk)
-
-        for patch in [Patch.objects.get(pk=p.pk) for p in self.patches]:
-            self.assertEqual(patch.state, state)
-
-    def test_state_change_invalid(self):
-        state = max(State.objects.all().values_list('id', flat=True)) + 1
-        orig_states = [patch.state for patch in self.patches]
-
-        response = self._test_state_change(state)
-
-        new_states = [Patch.objects.get(pk=p.pk).state for p in self.patches]
-        self.assertEqual(new_states, orig_states)
-        self.assertFormError(response, 'patchform', 'state',
-                             'Select a valid choice. That choice is not one '
-                             'of the available choices.')
-
-    def _test_delegate_change(self, delegate_str):
-        data = self.base_data.copy()
-        data.update({'delegate': delegate_str})
-        self._select_all_patches(data)
-
-        response = self.client.post(self.url, data)
-
-        self.assertContains(response, self.properties_form_id, status_code=200)
-        return response
-
-    def test_delegate_change_valid(self):
-        delegate = create_maintainer(self.project)
-
-        self._test_delegate_change(str(delegate.pk))
-
-        for patch in [Patch.objects.get(pk=p.pk) for p in self.patches]:
-            self.assertEqual(patch.delegate, delegate)
-
-    def test_delegate_clear(self):
-        self._test_delegate_change('')
-
-        for patch in [Patch.objects.get(pk=p.pk) for p in self.patches]:
-            self.assertEqual(patch.delegate, None)
diff --git a/patchwork/tests/views/__init__.py b/patchwork/tests/views/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/patchwork/tests/views/test_cover.py b/patchwork/tests/views/test_cover.py
new file mode 100644 (file)
index 0000000..6dc65bb
--- /dev/null
@@ -0,0 +1,86 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2016 Intel Corporation
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+from django.test import TestCase
+from django.urls import reverse
+
+from patchwork.tests.utils import create_cover
+from patchwork.tests.utils import create_cover_comment
+from patchwork.tests.utils import create_project
+
+
+class CoverViewTest(TestCase):
+
+    def test_redirect(self):
+        cover = create_cover()
+
+        requested_url = reverse('patch-detail',
+                                kwargs={'project_id': cover.project.linkname,
+                                        'msgid': cover.url_msgid})
+        redirect_url = reverse('cover-detail',
+                               kwargs={'project_id': cover.project.linkname,
+                                       'msgid': cover.url_msgid})
+
+        response = self.client.get(requested_url)
+        self.assertRedirects(response, redirect_url)
+
+    def test_old_detail_url(self):
+        cover = create_cover()
+
+        requested_url = reverse('cover-id-redirect',
+                                kwargs={'cover_id': cover.id})
+        redirect_url = reverse('cover-detail',
+                               kwargs={'project_id': cover.project.linkname,
+                                       'msgid': cover.url_msgid})
+
+        response = self.client.get(requested_url)
+        self.assertRedirects(response, redirect_url)
+
+    def test_old_mbox_url(self):
+        cover = create_cover()
+
+        requested_url = reverse('cover-mbox-redirect',
+                                kwargs={'cover_id': cover.id})
+        redirect_url = reverse('cover-mbox',
+                               kwargs={'project_id': cover.project.linkname,
+                                       'msgid': cover.url_msgid})
+
+        response = self.client.get(requested_url)
+        self.assertRedirects(response, redirect_url)
+
+    def test_invalid_project_id(self):
+        requested_url = reverse(
+            'cover-detail',
+            kwargs={'project_id': 'foo', 'msgid': 'bar'},
+        )
+        response = self.client.get(requested_url)
+        self.assertEqual(response.status_code, 404)
+
+    def test_invalid_cover_id(self):
+        project = create_project()
+        requested_url = reverse(
+            'cover-detail',
+            kwargs={'project_id': project.linkname, 'msgid': 'foo'},
+        )
+        response = self.client.get(requested_url)
+        self.assertEqual(response.status_code, 404)
+
+
+class CommentRedirectTest(TestCase):
+
+    def test_cover_redirect(self):
+        cover = create_cover()
+        comment_id = create_cover_comment(cover=cover).id
+
+        requested_url = reverse('comment-redirect',
+                                kwargs={'comment_id': comment_id})
+        redirect_url = '%s#%d' % (
+            reverse('cover-detail',
+                    kwargs={'project_id': cover.project.linkname,
+                            'msgid': cover.url_msgid}),
+            comment_id)
+
+        response = self.client.get(requested_url)
+        self.assertRedirects(response, redirect_url)
diff --git a/patchwork/tests/views/test_patch.py b/patchwork/tests/views/test_patch.py
new file mode 100644 (file)
index 0000000..1a1243c
--- /dev/null
@@ -0,0 +1,448 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2012 Jeremy Kerr <jk@ozlabs.org>
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+from datetime import datetime as dt
+from datetime import timedelta
+import re
+import unittest
+
+from django.conf import settings
+from django.test import TestCase
+from django.urls import reverse
+
+from patchwork.models import Check
+from patchwork.models import Patch
+from patchwork.models import State
+from patchwork.tests.utils import create_check
+from patchwork.tests.utils import create_maintainer
+from patchwork.tests.utils import create_patch
+from patchwork.tests.utils import create_patch_comment
+from patchwork.tests.utils import create_patches
+from patchwork.tests.utils import create_person
+from patchwork.tests.utils import create_project
+from patchwork.tests.utils import create_state
+from patchwork.tests.utils import create_user
+from patchwork.tests.utils import read_patch
+
+
+class EmptyPatchListTest(TestCase):
+
+    def test_empty_patch_list(self):
+        """Validates absence of table with zero patches."""
+        project = create_project()
+        url = reverse('patch-list', kwargs={'project_id': project.linkname})
+        response = self.client.get(url)
+        self.assertContains(response, 'No patches to display')
+
+
+class PatchListOrderingTest(TestCase):
+
+    patchmeta = [
+        ('AlCMyjOsx', 'AlxMyjOsx@nRbqkQV.wBw',
+         dt(2014, 3, 16, 13, 4, 50, 155643)),
+        ('MMZnrcDjT', 'MMmnrcDjT@qGaIfOl.tbk',
+         dt(2014, 1, 25, 13, 4, 50, 162814)),
+        ('WGirwRXgK', 'WGSrwRXgK@TriIETY.GhE',
+         dt(2014, 2, 14, 13, 4, 50, 169305)),
+        ('isjNIuiAc', 'issNIuiAc@OsEirYx.EJh',
+         dt(2014, 3, 15, 13, 4, 50, 176264)),
+        ('XkAQpYGws', 'XkFQpYGws@hzntTcm.JSE',
+         dt(2014, 1, 18, 13, 4, 50, 182493)),
+        ('uJuCPWMvi', 'uJACPWMvi@AVRBOBl.ecy',
+         dt(2014, 3, 12, 13, 4, 50, 189554)),
+        ('TyQmWtcbg', 'TylmWtcbg@DzrNeNH.JuB',
+         dt(2014, 2, 3, 13, 4, 50, 195685)),
+        ('FpvAhWRdX', 'FpKAhWRdX@agxnCAI.wFO',
+         dt(2014, 3, 15, 13, 4, 50, 201398)),
+        ('bmoYvnyWa', 'bmdYvnyWa@aeoPnlX.juy',
+         dt(2014, 3, 4, 13, 4, 50, 206800)),
+        ('CiReUQsAq', 'CiieUQsAq@DnOYRuf.TTI',
+         dt(2014, 3, 28, 13, 4, 50, 212169)),
+    ]
+
+    def setUp(self):
+        self.project = create_project()
+
+        for name, email, date in self.patchmeta:
+            person = create_person(name=name, email=email)
+            create_patch(submitter=person, project=self.project,
+                         date=date)
+
+    def _extract_patch_ids(self, response):
+        id_re = re.compile(r'<tr id="patch_row:(\d+)"')
+        ids = [int(m.group(1))
+               for m in id_re.finditer(response.content.decode())]
+
+        return ids
+
+    def _test_sequence(self, response, test_fn):
+        ids = self._extract_patch_ids(response)
+        self.assertTrue(bool(ids))
+        patches = [Patch.objects.get(id=i) for i in ids]
+        pairs = list(zip(patches, patches[1:]))
+
+        for p1, p2 in pairs:
+            test_fn(p1, p2)
+
+    def test_date_order(self):
+        url = reverse('patch-list',
+                      kwargs={'project_id': self.project.linkname})
+        response = self.client.get(url + '?order=date')
+
+        def test_fn(p1, p2):
+            self.assertLessEqual(p1.date, p2.date)
+
+        self._test_sequence(response, test_fn)
+
+    def test_date_reverse_order(self):
+        url = reverse('patch-list',
+                      kwargs={'project_id': self.project.linkname})
+        response = self.client.get(url + '?order=-date')
+
+        def test_fn(p1, p2):
+            self.assertGreaterEqual(p1.date, p2.date)
+
+        self._test_sequence(response, test_fn)
+
+    # TODO(stephenfin): Looks like this has been resolved in Django 2.1 [1]? If
+    # not, it should be possible [2]
+    #
+    # [1] https://code.djangoproject.com/ticket/30248
+    # [2] https://michaelsoolee.com/case-insensitive-sorting-sqlite/
+    @unittest.skipIf('sqlite3' in settings.DATABASES['default']['ENGINE'],
+                     'The sqlite3 backend does not support case insensitive '
+                     'ordering')
+    def test_submitter_order(self):
+        url = reverse('patch-list',
+                      kwargs={'project_id': self.project.linkname})
+        response = self.client.get(url + '?order=submitter')
+
+        def test_fn(p1, p2):
+            self.assertLessEqual(p1.submitter.name.lower(),
+                                 p2.submitter.name.lower())
+
+        self._test_sequence(response, test_fn)
+
+    @unittest.skipIf('sqlite3' in settings.DATABASES['default']['ENGINE'],
+                     'The sqlite3 backend does not support case insensitive '
+                     'ordering')
+    def test_submitter_reverse_order(self):
+        url = reverse('patch-list',
+                      kwargs={'project_id': self.project.linkname})
+        response = self.client.get(url + '?order=-submitter')
+
+        def test_fn(p1, p2):
+            self.assertGreaterEqual(p1.submitter.name.lower(),
+                                    p2.submitter.name.lower())
+
+        self._test_sequence(response, test_fn)
+
+
+class PatchListFilteringTest(TestCase):
+
+    def test_escaping(self):
+        """Validate escaping of filter fragments in a query string.
+
+        Stray ampersands should not get reflected back in the filter
+        links.
+        """
+        project = create_project()
+        url = reverse('patch-list', args=[project.linkname])
+
+        response = self.client.get(url + '?submitter=a%%26b=c')
+
+        self.assertEqual(response.status_code, 200)
+        self.assertNotContains(response, 'submitter=a&amp;b=c')
+        self.assertNotContains(response, 'submitter=a&b=c')
+
+    def test_utf8_handling(self):
+        """Validate handling of non-ascii characters."""
+        project = create_project()
+        url = reverse('patch-list', args=[project.linkname])
+
+        response = self.client.get(url + '?submitter=%%E2%%98%%83')
+
+        self.assertEqual(response.status_code, 200)
+
+
+class PatchViewTest(TestCase):
+
+    def test_redirect(self):
+        patch = create_patch()
+
+        requested_url = reverse('cover-detail',
+                                kwargs={'project_id': patch.project.linkname,
+                                        'msgid': patch.url_msgid})
+        redirect_url = reverse('patch-detail',
+                               kwargs={'project_id': patch.project.linkname,
+                                       'msgid': patch.url_msgid})
+
+        response = self.client.get(requested_url)
+        self.assertRedirects(response, redirect_url)
+
+    def test_comment_redirect(self):
+        patch = create_patch()
+        comment_id = create_patch_comment(patch=patch).id
+
+        requested_url = reverse('comment-redirect',
+                                kwargs={'comment_id': comment_id})
+        redirect_url = '%s#%d' % (
+            reverse('patch-detail',
+                    kwargs={'project_id': patch.project.linkname,
+                            'msgid': patch.url_msgid}),
+            comment_id)
+
+        response = self.client.get(requested_url)
+        self.assertRedirects(response, redirect_url)
+
+    def test_old_detail_url(self):
+        patch = create_patch()
+
+        requested_url = reverse('patch-id-redirect',
+                                kwargs={'patch_id': patch.id})
+        redirect_url = reverse('patch-detail',
+                               kwargs={'project_id': patch.project.linkname,
+                                       'msgid': patch.url_msgid})
+
+        response = self.client.get(requested_url)
+        self.assertRedirects(response, redirect_url)
+
+    def test_old_mbox_url(self):
+        patch = create_patch()
+
+        requested_url = reverse('patch-mbox-redirect',
+                                kwargs={'patch_id': patch.id})
+        redirect_url = reverse('patch-mbox',
+                               kwargs={'project_id': patch.project.linkname,
+                                       'msgid': patch.url_msgid})
+
+        response = self.client.get(requested_url)
+        self.assertRedirects(response, redirect_url)
+
+    def test_old_raw_url(self):
+        patch = create_patch()
+
+        requested_url = reverse('patch-raw-redirect',
+                                kwargs={'patch_id': patch.id})
+        redirect_url = reverse('patch-raw',
+                               kwargs={'project_id': patch.project.linkname,
+                                       'msgid': patch.url_msgid})
+
+        response = self.client.get(requested_url)
+        self.assertRedirects(response, redirect_url)
+
+    def test_escaping(self):
+        # Warning: this test doesn't guarantee anything - it only tests some
+        # fields
+        unescaped_string = 'blah<b>TEST</b>blah'
+        patch = create_patch()
+        patch.diff = unescaped_string
+        patch.commit_ref = unescaped_string
+        patch.pull_url = unescaped_string
+        patch.name = unescaped_string
+        patch.msgid = '<' + unescaped_string + '>'
+        patch.headers = unescaped_string
+        patch.content = unescaped_string
+        patch.save()
+        requested_url = reverse('patch-detail',
+                                kwargs={'project_id': patch.project.linkname,
+                                        'msgid': patch.url_msgid})
+        response = self.client.get(requested_url)
+        self.assertNotIn('<b>TEST</b>'.encode('utf-8'), response.content)
+
+    def test_invalid_project_id(self):
+        requested_url = reverse(
+            'patch-detail',
+            kwargs={'project_id': 'foo', 'msgid': 'bar'},
+        )
+        response = self.client.get(requested_url)
+        self.assertEqual(response.status_code, 404)
+
+    def test_invalid_patch_id(self):
+        project = create_project()
+        requested_url = reverse(
+            'patch-detail',
+            kwargs={'project_id': project.linkname, 'msgid': 'foo'},
+        )
+        response = self.client.get(requested_url)
+        self.assertEqual(response.status_code, 404)
+
+    def test_patch_with_checks(self):
+        user = create_user()
+        patch = create_patch()
+        check_a = create_check(
+            patch=patch, user=user, context='foo', state=Check.STATE_FAIL,
+            date=(dt.utcnow() - timedelta(days=1)))
+        create_check(
+            patch=patch, user=user, context='foo', state=Check.STATE_SUCCESS)
+        check_b = create_check(
+            patch=patch, user=user, context='bar', state=Check.STATE_PENDING)
+        requested_url = reverse(
+            'patch-detail',
+            kwargs={
+                'project_id': patch.project.linkname,
+                'msgid': patch.url_msgid,
+            },
+        )
+        response = self.client.get(requested_url)
+
+        # the response should contain checks
+        self.assertContains(response, '<h2>Checks</h2>')
+
+        # and it should only show the unique checks
+        self.assertEqual(
+            1, response.content.decode().count(
+                f'<td>{check_a.user}/{check_a.context}</td>'
+            ))
+        self.assertEqual(
+            1, response.content.decode().count(
+                f'<td>{check_b.user}/{check_b.context}</td>'
+            ))
+
+
+class PatchUpdateTest(TestCase):
+
+    properties_form_id = 'patchform-properties'
+
+    def setUp(self):
+        self.project = create_project()
+        self.user = create_maintainer(self.project)
+        self.patches = create_patches(3, project=self.project)
+
+        self.client.login(username=self.user.username,
+                          password=self.user.username)
+
+        self.url = reverse('patch-list', args=[self.project.linkname])
+        self.base_data = {
+            'action': 'Update',
+            'project': str(self.project.id),
+            'form': 'patchlistform',
+            'archived': '*',
+            'delegate': '*',
+            'state': '*'
+        }
+
+    def _select_all_patches(self, data):
+        for patch in self.patches:
+            data['patch_id:%d' % patch.id] = 'checked'
+
+    def test_archiving_patches(self):
+        data = self.base_data.copy()
+        data.update({'archived': 'True'})
+        self._select_all_patches(data)
+
+        response = self.client.post(self.url, data)
+
+        self.assertContains(response, 'No patches to display',
+                            status_code=200)
+        # Don't use the cached version of patches: retrieve from the DB
+        for patch in [Patch.objects.get(pk=p.pk) for p in self.patches]:
+            self.assertTrue(patch.archived)
+
+    def test_unarchiving_patches(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._select_all_patches(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 _test_state_change(self, state):
+        data = self.base_data.copy()
+        data.update({'state': str(state)})
+        self._select_all_patches(data)
+
+        response = self.client.post(self.url, data)
+
+        self.assertContains(response, self.properties_form_id,
+                            status_code=200)
+        return response
+
+    def test_state_change_valid(self):
+        state = create_state()
+
+        self._test_state_change(state.pk)
+
+        for patch in [Patch.objects.get(pk=p.pk) for p in self.patches]:
+            self.assertEqual(patch.state, state)
+
+    def test_state_change_invalid(self):
+        state = max(State.objects.all().values_list('id', flat=True)) + 1
+        orig_states = [patch.state for patch in self.patches]
+
+        response = self._test_state_change(state)
+
+        new_states = [Patch.objects.get(pk=p.pk).state for p in self.patches]
+        self.assertEqual(new_states, orig_states)
+        self.assertFormError(response, 'patchform', 'state',
+                             'Select a valid choice. That choice is not one '
+                             'of the available choices.')
+
+    def _test_delegate_change(self, delegate_str):
+        data = self.base_data.copy()
+        data.update({'delegate': delegate_str})
+        self._select_all_patches(data)
+
+        response = self.client.post(self.url, data)
+
+        self.assertContains(response, self.properties_form_id, status_code=200)
+        return response
+
+    def test_delegate_change_valid(self):
+        delegate = create_maintainer(self.project)
+
+        self._test_delegate_change(str(delegate.pk))
+
+        for patch in [Patch.objects.get(pk=p.pk) for p in self.patches]:
+            self.assertEqual(patch.delegate, delegate)
+
+    def test_delegate_clear(self):
+        self._test_delegate_change('')
+
+        for patch in [Patch.objects.get(pk=p.pk) for p in self.patches]:
+            self.assertEqual(patch.delegate, None)
+
+
+class UTF8PatchViewTest(TestCase):
+
+    def setUp(self):
+        patch_content = read_patch('0002-utf-8.patch', encoding='utf-8')
+        self.patch = create_patch(diff=patch_content)
+
+    def test_patch_view(self):
+        response = self.client.get(reverse(
+            'patch-detail', args=[self.patch.project.linkname,
+                                  self.patch.url_msgid]))
+        self.assertContains(response, self.patch.name)
+
+    def test_mbox_view(self):
+        response = self.client.get(
+            reverse('patch-mbox', args=[self.patch.project.linkname,
+                                        self.patch.url_msgid]))
+        self.assertEqual(response.status_code, 200)
+        self.assertTrue(self.patch.diff in response.content.decode('utf-8'))
+
+    def test_raw_view(self):
+        response = self.client.get(reverse('patch-raw',
+                                           args=[self.patch.project.linkname,
+                                                 self.patch.url_msgid]))
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.content.decode('utf-8'), self.patch.diff)
+
+
+class UTF8HeaderPatchViewTest(UTF8PatchViewTest):
+
+    def setUp(self):
+        author = create_person(name=u'P\xe4tch Author')
+        patch_content = read_patch('0002-utf-8.patch', encoding='utf-8')
+        self.patch = create_patch(submitter=author, diff=patch_content)
similarity index 50%
rename from patchwork/tests/test_user.py
rename to patchwork/tests/views/test_user.py
index b476dbf1858ed65c6daf1d472bd4a35794578b10..22bb9839bcff818b0aeeb7943a6508405155070b 100644 (file)
@@ -5,16 +5,17 @@
 
 from django.contrib.auth.models import User
 from django.core import mail
+from django.test.client import Client
 from django.test import TestCase
 from django.urls import reverse
 
 from patchwork.models import EmailConfirmation
 from patchwork.models import Person
 from patchwork.models import UserProfile
+from patchwork.tests import utils
 from patchwork.tests.utils import create_bundle
 from patchwork.tests.utils import create_user
 from patchwork.tests.utils import error_strings
-from patchwork.tests import utils
 
 
 def _confirmation_url(conf):
@@ -37,10 +38,208 @@ class _UserTestCase(TestCase):
                           password=self.password)
 
 
-class UserPersonRequestTest(_UserTestCase):
+class TestUser(object):
+    firstname = 'Test'
+    lastname = 'User'
+    fullname = ' '.join([firstname, lastname])
+    username = 'testuser'
+    email = 'test@example.com'
+    password = 'foobar'
+
+
+class RegistrationTest(TestCase):
 
     def setUp(self):
-        super(UserPersonRequestTest, self).setUp()
+        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 test_registration_form(self):
+        response = self.client.get('/register/')
+        self.assertEqual(response.status_code, 200)
+        self.assertTemplateUsed(response, 'patchwork/registration.html')
+
+    def test_blank_fields(self):
+        for field in ['username', 'email', 'password']:
+            data = self.default_data.copy()
+            del data[field]
+            response = self.client.post('/register/', data)
+            self.assertEqual(response.status_code, 200)
+            self.assertFormError(response, 'form', field, self.required_error)
+
+    def test_invalid_username(self):
+        data = self.default_data.copy()
+        data['username'] = 'invalid user'
+        response = self.client.post('/register/', data)
+        self.assertEqual(response.status_code, 200)
+        self.assertFormError(response, 'form', 'username', self.invalid_error)
+
+    def test_existing_username(self):
+        user = create_user()
+        data = self.default_data.copy()
+        data['username'] = user.username
+        response = self.client.post('/register/', data)
+        self.assertEqual(response.status_code, 200)
+        self.assertFormError(
+            response, 'form', 'username',
+            'This username is already taken. Please choose another.')
+
+    def test_existing_email(self):
+        user = create_user()
+        data = self.default_data.copy()
+        data['email'] = user.email
+        response = self.client.post('/register/', data)
+        self.assertEqual(response.status_code, 200)
+        self.assertFormError(
+            response, 'form', 'email',
+            'This email address is already in use for the account '
+            '"%s".\n' % user.username)
+
+    def test_valid_registration(self):
+        response = self.client.post('/register/', self.default_data)
+        self.assertEqual(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.assertEqual(users.count(), 1)
+        user = users[0]
+        self.assertEqual(user.username, self.user.username)
+        self.assertEqual(user.email, self.user.email)
+        self.assertEqual(user.is_active, False)
+
+        # check for confirmation object
+        confs = EmailConfirmation.objects.filter(
+            user=user, type='registration')
+        self.assertEqual(len(confs), 1)
+        conf = confs[0]
+        self.assertEqual(conf.email, self.user.email)
+
+        # check for a sent mail
+        self.assertEqual(len(mail.outbox), 1)
+        msg = mail.outbox[0]
+        self.assertEqual(msg.subject, 'Patchwork account confirmation')
+        self.assertIn(self.user.email, msg.to)
+        self.assertIn(_confirmation_url(conf), msg.body)
+
+        # ...and that the URL is valid
+        response = self.client.get(_confirmation_url(conf))
+        self.assertEqual(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 test_valid(self):
+        """Test the success path."""
+        self.assertEqual(EmailConfirmation.objects.count(), 0)
+        response = self.client.post('/register/', self.default_data)
+        self.assertEqual(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.assertEqual(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 test_new_person_setup(self):
+        """Ensure a new Person is created after account setup.
+
+        Create an account for a never before seen email. Check that a Person
+        object is created after registration and has the correct details.
+        """
+        # register
+        self.assertEqual(EmailConfirmation.objects.count(), 0)
+        response = self.client.post('/register/', self.default_data)
+        self.assertEqual(response.status_code, 200)
+        self.assertFalse(Person.objects.exists())
+
+        # confirm
+        conf = EmailConfirmation.objects.filter()[0]
+        response = self.client.get(_confirmation_url(conf))
+        self.assertEqual(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.assertEqual(person.name, self.user.fullname)
+
+    def test_existing_person_setup(self):
+        """Ensure an existing person is linked after account setup.
+
+        Create an account for a user using an email we've previously seen.
+        Check that the person object is updated after registration with the
+        correct details.
+        """
+        person = Person(name=self.user.fullname, email=self.user.email)
+        person.save()
+
+        # register
+        self.assertEqual(EmailConfirmation.objects.count(), 0)
+        response = self.client.post('/register/', self.default_data)
+        self.assertEqual(response.status_code, 200)
+
+        # confirm
+        conf = EmailConfirmation.objects.filter()[0]
+        response = self.client.get(_confirmation_url(conf))
+        self.assertEqual(response.status_code, 200)
+
+        person = Person.objects.get(email=self.user.email)
+
+        self.assertEqual(person.name, self.user.fullname)
+
+    def test_existing_person_unmodified(self):
+        """Ensure an existing person is not linked until registration is done.
+
+        Create an account for a user using an email we've previously seen but
+        don't confirm it. Check that the person object is not updated yet.
+        """
+        person = Person(name=self.user.fullname, email=self.user.email)
+        person.save()
+
+        # register
+        data = self.default_data.copy()
+        data['first_name'] = 'invalid'
+        data['last_name'] = 'invalid'
+        self.assertEqual(data['email'], person.email)
+        response = self.client.post('/register/', data)
+        self.assertEqual(response.status_code, 200)
+
+        self.assertEqual(
+            Person.objects.get(pk=person.pk).name, self.user.fullname)
+
+
+class UserLinkTest(_UserTestCase):
+
+    def setUp(self):
+        super().setUp()
         self.secondary_email = _generate_secondary_email(self.user)
 
     def test_user_person_request_form(self):
@@ -88,7 +287,7 @@ class UserPersonRequestTest(_UserTestCase):
         self.assertTemplateUsed(response, 'patchwork/user-link-confirm.html')
 
 
-class UserPersonConfirmTest(TestCase):
+class ConfirmationTest(TestCase):
 
     def setUp(self):
         self.user = create_user(link_person=False)
@@ -122,7 +321,37 @@ class UserPersonConfirmTest(TestCase):
         self.assertEqual(conf.active, False)
 
 
-class UserLoginRedirectTest(TestCase):
+class InvalidConfirmationTest(TestCase):
+
+    def setUp(self):
+        self.user = create_user()
+        self.secondary_email = _generate_secondary_email(self.user)
+
+        self.conf = EmailConfirmation(type='userperson',
+                                      email=self.secondary_email,
+                                      user=self.user)
+        self.conf.save()
+
+    def test_inactive_confirmation(self):
+        self.conf.active = False
+        self.conf.save()
+        response = self.client.get(_confirmation_url(self.conf))
+        self.assertEqual(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 test_expired_confirmation(self):
+        self.conf.date -= self.conf.validity
+        self.conf.save()
+        response = self.client.get(_confirmation_url(self.conf))
+        self.assertEqual(response.status_code, 200)
+        self.assertTemplateUsed(response, 'patchwork/confirm-error.html')
+        self.assertEqual(response.context['error'], 'expired')
+        self.assertEqual(response.context['conf'], self.conf)
+
+
+class LoginRedirectTest(TestCase):
 
     def test_user_login_redirect(self):
         url = reverse('user-profile')
@@ -178,7 +407,7 @@ class UserProfileTest(_UserTestCase):
         self.assertEqual(user_profile.items_per_page, old_ppp)
 
 
-class UserPasswordChangeTest(_UserTestCase):
+class PasswordChangeTest(_UserTestCase):
 
     def test_password_change_form(self):
         response = self.client.get(reverse('password_change'))