from unittest import mock from urllib.parse import parse_qs, urlparse from django.core.urlresolvers import reverse from django.http import QueryDict from django.test import Client from django.utils import timezone from .utils import create_root, create_team, create_user class TestCaseMixin: 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) if request.user.is_authenticated() else 'anonymous' ), 'code': response.status_code, } ) def assertForbiddenKfet(self, response, form_ctx='form'): try: self.assertEqual(response.status_code, 200) try: form = response.context[form_ctx] self.assertIn("Permission refusée", form.non_field_errors()) except (AssertionError, AttributeError, KeyError): messages = [str(msg) for msg in response.context['messages']] self.assertIn("Permission refusée", messages) except AssertionError: request = response.wsgi_request raise AssertionError( "%(http_method)s request at %(path)s should raise an error " "for %(username)s user.\n" "Cannot find any errors in non-field errors of form " "'%(form_ctx)s', nor in messages." % { 'http_method': request.method, 'path': request.get_full_path(), 'username': ( "'%s'" % request.user if request.user.is_authenticated() else 'anonymous' ), 'form_ctx': form_ctx, } ) def assertInstanceExpected(self, instance, expected): for attr, expected_value in expected.items(): value = getattr(instance, attr) if callable(value): value = value() self.assertEqual(value, expected_value) def assertUrlsEqual(self, actual, expected): if type(expected) == dict: parsed = urlparse(actual) checks = ['scheme', 'netloc', 'path', 'params'] for check in checks: self.assertEqual( getattr(parsed, check), expected.get(check, ''), ) self.assertDictEqual( parse_qs(parsed.query), expected.get('query', {}), ) else: self.assertEqual(actual, expected) class ViewTestCaseMixin(TestCaseMixin): url_name = None url_expected = None http_methods = ['GET'] 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) # A test can mock 'django.utils.timezone.now' and give this as return # value. E.g. it is useful if the test checks values of 'auto_now' or # 'auto_now_add' fields. self.now = timezone.now() 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 def get_user(self, label): if self.auth_user is not None: return self.auth_user return self.auth_user_mapping.get(label) @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 test_urls(self): for url, conf in zip(self.t_urls, self.urls_conf): self.assertEqual(url, conf['expected']) def test_forbidden(self): for method in self.http_methods: for user in self.auth_forbidden: for url in self.t_urls: self.check_forbidden(method, url, user) def check_forbidden(self, method, url, user=None): method = method.lower() client = Client() if user is not None: client.login(username=user, password=user) send_request = getattr(client, method) data = getattr(self, '{}_data'.format(method), {}) r = send_request(url, data) self.assertForbidden(r)