From c9aac8a49dec92e31a1220fc700f65c6b82fdbcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Thu, 10 Aug 2017 15:02:08 +0200 Subject: [PATCH] [WIP] Tests for kfet views --- kfet/tests/test_views.py | 360 ++++++++++++++++++++++++++++++++++++--- kfet/tests/testcases.py | 142 +++++++++++++++ kfet/tests/utils.py | 105 ++++++++++++ 3 files changed, 585 insertions(+), 22 deletions(-) create mode 100644 kfet/tests/testcases.py create mode 100644 kfet/tests/utils.py diff --git a/kfet/tests/test_views.py b/kfet/tests/test_views.py index ec9d6ffc..f7457786 100644 --- a/kfet/tests/test_views.py +++ b/kfet/tests/test_views.py @@ -1,26 +1,243 @@ +import json from decimal import Decimal -from unittest.mock import patch -from django.test import TestCase, Client -from django.contrib.auth.models import User +from django.contrib.auth.models import Group, Permission +from django.core.urlresolvers import reverse +from django.test import Client, TestCase from django.utils import timezone -from ..models import Account, OperationGroup, Checkout, Operation +from ..models import Account, Checkout, Operation, OperationGroup + +from .testcases import ViewTestCaseMixin +from .utils import create_team, create_user -class AccountTests(TestCase): - """Account related views""" +class LoginGenericTeamViewTests(ViewTestCaseMixin, TestCase): + url_name = 'kfet.login.genericteam' + url_expected = '/k-fet/login/genericteam' + + auth_user = 'team' + auth_forbidden = [None, 'user'] + + def test_ok(self): + r = self.client.get(self.url) + self.assertEqual(r.status_code, 200) + logged_in_username = r.wsgi_request.user.username + self.assertEqual(logged_in_username, 'kfet_genericteam') + + +class AccountListViewTests(ViewTestCaseMixin, TestCase): + url_name = 'kfet.account' + url_expected = '/k-fet/accounts/' + + auth_user = 'team' + auth_forbidden = [None, 'user'] + + def test_ok(self): + r = self.client.get(self.url) + self.assertEqual(r.status_code, 200) + + +class AccountValidFreeTests(ViewTestCaseMixin, TestCase): + url_name = 'kfet.account.is_validandfree.ajax' + url_expected = '/k-fet/accounts/is_validandfree' + + auth_user = 'team' + auth_forbidden = [None, 'user'] + + def test_ok_isvalid_isfree(self): + """Upper case trigramme not taken is valid and free.""" + r = self.client.get(self.url, {'trigramme': 'AAA'}) + self.assertDictEqual(json.loads(r.content.decode('utf-8')), { + 'is_valid': True, + 'is_free': True, + }) + + def test_ok_isvalid_notfree(self): + """Already taken trigramme is not free, but valid.""" + r = self.client.get(self.url, {'trigramme': '000'}) + self.assertDictEqual(json.loads(r.content.decode('utf-8')), { + 'is_valid': True, + 'is_free': False, + }) + + def test_ok_notvalid_isfree(self): + """Lower case if forbidden but free.""" + r = self.client.get(self.url, {'trigramme': 'aaa'}) + self.assertDictEqual(json.loads(r.content.decode('utf-8')), { + 'is_valid': False, + 'is_free': True, + }) + + +class AccountCreateViewTests(ViewTestCaseMixin, TestCase): + url_name = 'kfet.account.create' + url_expected = '/k-fet/accounts/new' + + auth_user = 'team' + auth_forbidden = [None, 'user'] + + @property + def users_extra(self): + return { + 'team__add_account': create_team( + 'team__add_account', '101', + perms=['kfet.add_account'], + ), + } + + def test_get_ok(self): + r = self.client.get(self.url) + self.assertEqual(r.status_code, 200) + + def test_post_ok(self): + post_data = { + 'trigramme': 'AAA', + 'username': 'plopplopplop', + 'first_name': 'first', + 'last_name': 'last', + 'email': 'email@domain.net', + } + + client = Client() + client.login( + username='team__add_account', + password='team__add_account', + ) + r = client.post(self.url, post_data) + + self.assertRedirects(r, self.url) + a = Account.objects.get(trigramme='AAA') + self.assertEqual(a.username, 'plopplopplop') + + def test_post_forbidden(self): + post_data = { + 'trigramme': 'AAA', + 'username': 'plopplopplop', + 'first_name': 'first', + 'last_name': 'last', + 'email': 'email@domain.net', + } + + # A team member (without kfet.add_account) is authenticated with + # self.client. + r = self.client.post(self.url, post_data) + + self.assertEqual(r.status_code, 200) + with self.assertRaises(Account.DoesNotExist): + Account.objects.get(trigramme='AAA') + + +class AccountCreateAjaxViewTests(ViewTestCaseMixin, TestCase): + urls_conf = [ + { + 'name': 'kfet.account.create.fromuser', + 'kwargs': {'username': 'user'}, + 'expected': '/k-fet/accounts/new/user/user', + }, + { + 'name': 'kfet.account.create.fromclipper', + 'kwargs': { + 'login_clipper': 'myclipper', + 'fullname': 'first last1 last2', + }, + 'expected': ( + '/k-fet/accounts/new/clipper/myclipper/first%20last1%20last2' + ), + }, + { + 'name': 'kfet.account.create.empty', + 'expected': '/k-fet/accounts/new/empty', + }, + ] + + auth_user = 'team' + auth_forbidden = [None, 'user'] + + def test_fromuser(self): + r = self.client.get(self.t_urls[0]) + + user = self.users['user'] + + self.assertEqual(r.status_code, 200) + self.assertEqual(r.context['user_form'].instance, user) + self.assertEqual(r.context['cof_form'].instance, user.profile) + self.assertIn('account_form', r.context) + + def test_fromclipper(self): + r = self.client.get(self.t_urls[1]) + + self.assertEqual(r.status_code, 200) + self.assertIn('user_form', r.context) + self.assertIn('cof_form', r.context) + self.assertIn('account_form', r.context) + + def test_empty(self): + r = self.client.get(self.t_urls[0]) + + self.assertEqual(r.status_code, 200) + self.assertIn('user_form', r.context) + self.assertIn('cof_form', r.context) + self.assertIn('account_form', r.context) + + +class AccountCreateAutocompleteViewTests(ViewTestCaseMixin, TestCase): + url_name = 'kfet.account.create.autocomplete' + url_expected = '/k-fet/autocomplete/account_new' + + auth_user = 'team' + auth_forbidden = [None, 'user'] + + def test_ok(self): + r = self.client.get(self.url, {'q': 'first'}) + self.assertEqual(r.status_code, 200) + self.assertListEqual(list(r.context['users_notcof']), []) + self.assertListEqual(list(r.context['users_cof']), []) + self.assertListEqual( + list(r.context['kfet']), + [(self.accounts['user'], self.users['user'])], + ) + + +class AccountSearchViewTests(ViewTestCaseMixin, TestCase): + url_name = 'kfet.account.search.autocomplete' + url_expected = '/k-fet/autocomplete/account_search' + + auth_user = 'team' + auth_forbidden = [None, 'user'] + + def test_ok(self): + r = self.client.get(self.url, {'q': 'first'}) + self.assertEqual(r.status_code, 200) + self.assertListEqual( + list(r.context['accounts']), + [('000', 'first last')], + ) + + +class AccountReadViewTests(ViewTestCaseMixin, TestCase): + url_name = 'kfet.account.read' + url_kwargs = {'trigramme': '001'} + url_expected = '/k-fet/accounts/001' + + auth_user = 'team' + auth_forbidden = [None, 'user'] + + @property + def users_extra(self): + return { + 'user1': create_user('user1', '001'), + } def setUp(self): - # A user and its account - self.user = User.objects.create_user(username="foobar", password="foo") - acc = Account.objects.create( - trigramme="FOO", cofprofile=self.user.profile - ) + super().setUp() + + user1_acc = self.accounts['user1'] + team_acc = self.accounts['team'] # Dummy operations and operation groups checkout = Checkout.objects.create( - created_by=acc, name="checkout", + created_by=team_acc, name="checkout", valid_from=timezone.now(), valid_to=timezone.now() + timezone.timedelta(days=365) ) @@ -30,7 +247,7 @@ class AccountTests(TestCase): ] OperationGroup.objects.bulk_create([ OperationGroup( - on_acc=acc, checkout=checkout, at=at, is_cof=False, + on_acc=user1_acc, checkout=checkout, at=at, is_cof=False, amount=amount ) for (at, amount) in opeg_data @@ -47,13 +264,112 @@ class AccountTests(TestCase): amount=Decimal('3') ) - @patch('gestioncof.signals.messages') - def test_account_read(self, mock_messages): - """We can query the "Account - Read" page.""" + def test_ok(self): + """We can query the "Account - Read" page.""" + r = self.client.get(self.url) + self.assertEqual(r.status_code, 200) + + def test_ok_self(self): client = Client() - self.assertTrue(client.login( - username="foobar", - password="foo" - )) - resp = client.get("/k-fet/accounts/FOO") - self.assertEqual(200, resp.status_code) + client.login(username='user1', password='user1') + r = client.get(self.url) + self.assertEqual(r.status_code, 200) + + +class AccountUpdateViewTests(ViewTestCaseMixin, TestCase): + url_name = 'kfet.account.update' + url_kwargs = {'trigramme': '001'} + url_expected = '/k-fet/accounts/001/edit' + + auth_user = 'team' + auth_forbidden = [None, 'user'] + + @property + def users_extra(self): + return { + 'user1': create_user('user1', '001'), + } + + def test_ok(self): + r = self.client.get(self.url) + self.assertEqual(r.status_code, 200) + + def test_ok_self(self): + client = Client() + client.login(username='user1', password='user1') + r = client.get(self.url) + self.assertEqual(r.status_code, 200) + + +class BaseAccountGroupViewTests(ViewTestCaseMixin): + auth_user = 'team__manage_perms' + auth_forbidden = [None, 'user', 'team'] + + @property + def users_extra(self): + return { + 'team__manage_perms': create_team( + 'team__manage_perms', '101', + perms=['kfet.manage_perms'], + ), + } + + +class AccountGroupListViewTests(BaseAccountGroupViewTests, TestCase): + url_name = 'kfet.account.group' + url_expected = '/k-fet/accounts/groups' + + def test_ok(self): + r = self.client.get(self.url) + self.assertEqual(r.status_code, 200) + + self.assertQuerysetEqual( + r.context['groups'], + Group.objects.filter(name__icontains='K-Fêt'), + ordered=False, + ) + + +class AccountGroupCreateViewTests(BaseAccountGroupViewTests, TestCase): + url_name = 'kfet.account.group.create' + url_expected = '/k-fet/accounts/groups/new' + + def test_ok(self): + r = self.client.get(self.url) + self.assertEqual(r.status_code, 200) + + +class AccountGroupUpdateViewTests(BaseAccountGroupViewTests, TestCase): + url_name = 'kfet.account.group.update' + url_kwargs = {'pk': 42} + url_expected = '/k-fet/accounts/groups/42/edit' + + def setUp(self): + super().setUp() + self.group1 = Group.objects.create(pk=42, name='K-Fêt - Group') + self.group1.permissions = [ + Permission.objects.get( + content_type__app_label='kfet', + codename='is_team', + ) + ] + + def test_get_ok(self): + r = self.client.get(self.url) + self.assertEqual(r.status_code, 200) + + def test_post_ok(self): + post_data = { + 'name': 'Group42', + 'permissions': ( + self.group1.permissions + .values_list('pk', flat=True) + ), + } + + r = self.client.post(self.url, post_data) + + self.assertRedirects(r, reverse('kfet.account.group')) + + self.group1.refresh_from_db() + self.assertEqual(self.group1.name, 'K-Fêt Group42') diff --git a/kfet/tests/testcases.py b/kfet/tests/testcases.py new file mode 100644 index 00000000..c2e1b848 --- /dev/null +++ b/kfet/tests/testcases.py @@ -0,0 +1,142 @@ +from unittest import mock + +from django.core.urlresolvers import reverse +from django.http import QueryDict +from django.test import Client + +from .utils import create_root, create_team, create_user + + +class ViewTestCaseMixin: + url_name = None + url_expected = None + + auth_user = None + auth_forbidden = [] + + def setUp(self): + # Signals handlers on login/logout send messages. + # Due to the way the Django' test Client performs login, this raise an + # error. As workaround, we mock the Django' messages module. + patcher_messages = mock.patch('gestioncof.signals.messages') + patcher_messages.start() + self.addCleanup(patcher_messages.stop) + + self.users = {} + self.accounts = {} + + for label, user in {**self.users_base, **self.users_extra}.items(): + self.register_user(label, user) + + if self.auth_user: + # The wrapper is a sanity check. + self.assertTrue( + self.client.login( + username=self.auth_user, + password=self.auth_user, + ) + ) + + @property + def users_base(self): + # Format desc: username, password, trigramme + return { + # user, user, 000 + 'user': create_user(), + # team, team, 100 + 'team': create_team(), + # root, root, 200 + 'root': create_root(), + } + + @property + def users_extra(self): + return {} + + def register_user(self, label, user): + self.users[label] = user + if hasattr(user.profile, 'account_kfet'): + self.accounts[label] = user.profile.account_kfet + + @property + def urls_conf(self): + return [{ + 'name': self.url_name, + 'args': getattr(self, 'url_args', []), + 'kwargs': getattr(self, 'url_kwargs', {}), + 'expected': self.url_expected, + }] + + @property + def t_urls(self): + return [ + reverse( + url_conf['name'], + args=url_conf.get('args', []), + kwargs=url_conf.get('kwargs', {}), + ) + for url_conf in self.urls_conf] + + @property + def url(self): + return self.t_urls[0] + + def assertForbidden(self, response): + request = response.wsgi_request + + try: + try: + # Is this an HTTP Forbidden response ? + self.assertEqual(response.status_code, 403) + except AssertionError: + # A redirection to the login view is fine too. + + # Let's build the login url with the 'next' param on current + # page. + full_path = request.get_full_path() + + querystring = QueryDict(mutable=True) + querystring['next'] = full_path + + login_url = '/login?' + querystring.urlencode(safe='/') + + # We don't focus on what the login view does. + # So don't fetch the redirect. + self.assertRedirects( + response, login_url, + fetch_redirect_response=False, + ) + except AssertionError: + raise AssertionError( + "%(http_method)s request at %(path)s should be forbidden for " + "%(username)s user.\n" + "Response isn't 403, nor a redirect to login view. Instead, " + "response code is %(code)d." % { + 'http_method': request.method, + 'path': request.get_full_path(), + 'username': ( + "'{}'".format(request.user.username) + if request.user.username + else 'anonymous' + ), + 'code': response.status_code, + } + ) + + def assertForbiddenKfet(self, response): + self.assertEqual(response.status_code, 200) + form = response.context['form'] + self.assertIn("Permission refusée", form.non_field_errors) + + def test_urls(self): + for url, conf in zip(self.t_urls, self.urls_conf): + self.assertEqual(url, conf['expected']) + + def test_forbidden(self): + for creds in self.auth_forbidden: + for url in self.t_urls: + client = Client() + if creds is not None: + client.login(username=creds, password=creds) + r = client.get(url) + self.assertForbidden(r) diff --git a/kfet/tests/utils.py b/kfet/tests/utils.py new file mode 100644 index 00000000..4b739003 --- /dev/null +++ b/kfet/tests/utils.py @@ -0,0 +1,105 @@ +from django.contrib.auth import get_user_model +from django.contrib.auth.models import Permission + +from ..models import Account + + +User = get_user_model() + + +def user_add_perms(user, perms_labels): + """ + Add perms to a user. + + Args: + user (User instance) + perms (list of str 'app.perm_name') + + Returns: + The same user (refetched from DB to avoid missing perms) + + """ + u_labels = set(perms_labels) + + perms = [] + for label in u_labels: + app_label, codename = label.split('.', 1) + perms.append( + Permission.objects.get( + content_type__app_label=app_label, + codename=codename, + ) + ) + + user.user_permissions.add(*perms) + + # If permissions have already been fetched for this user, we need to reload + # it to avoid using of the previous permissions cache. + # https://docs.djangoproject.com/en/1.11/topics/auth/default/#permission-caching + return User.objects.get(pk=user.pk) + + +def _create_user_and_account(user_attrs, account_attrs, perms=None): + user_attrs.setdefault('password', user_attrs['username']) + user = User.objects.create_user(**user_attrs) + + account_attrs['cofprofile'] = user.profile + kfet_pwd = account_attrs.pop('password', None) + + account = Account.objects.create(**account_attrs) + + if perms is not None: + user = user_add_perms(user, perms) + + if 'kfet.is_team' in perms: + if kfet_pwd is None: + kfet_pwd = 'kfetpwd_{}'.format(user_attrs['password']) + account.change_pwd(kfet_pwd) + account.save() + + return user + + +def create_user(username='user', trigramme='000', **kwargs): + user_attrs = kwargs.setdefault('user_attrs', {}) + + user_attrs.setdefault('username', username) + user_attrs.setdefault('first_name', 'first') + user_attrs.setdefault('last_name', 'last') + user_attrs.setdefault('email', 'mail@user.net') + + account_attrs = kwargs.setdefault('account_attrs', {}) + account_attrs.setdefault('trigramme', trigramme) + + return _create_user_and_account(**kwargs) + + +def create_team(username='team', trigramme='100', **kwargs): + user_attrs = kwargs.setdefault('user_attrs', {}) + + user_attrs.setdefault('username', username) + user_attrs.setdefault('first_name', 'team') + user_attrs.setdefault('last_name', 'member') + user_attrs.setdefault('email', 'mail@team.net') + + account_attrs = kwargs.setdefault('account_attrs', {}) + account_attrs.setdefault('trigramme', trigramme) + + perms = kwargs.setdefault('perms', []) + perms.append('kfet.is_team') + + return _create_user_and_account(**kwargs) + + +def create_root(username='root', trigramme='200', **kwargs): + user_attrs = kwargs.setdefault('user_attrs', {}) + + user_attrs.setdefault('username', username) + user_attrs.setdefault('first_name', 'super') + user_attrs.setdefault('last_name', 'user') + user_attrs.setdefault('email', 'mail@root.net') + + account_attrs = kwargs.setdefault('account_attrs', {}) + account_attrs.setdefault('trigramme', trigramme) + + return _create_user_and_account(**kwargs)