diff --git a/bda/tests/testcases.py b/bda/tests/mixins.py similarity index 97% rename from bda/tests/testcases.py rename to bda/tests/mixins.py index f5ac7f83..a4ba057b 100644 --- a/bda/tests/testcases.py +++ b/bda/tests/mixins.py @@ -4,7 +4,7 @@ from django.conf import settings from django.core.management import call_command from django.utils import timezone -from shared.tests.testcases import ViewTestCaseMixin +from shared.tests.mixins import ViewTestCaseMixin from ..models import CategorieSpectacle, Salle, Spectacle, Tirage from .utils import create_user diff --git a/bda/tests/test_views.py b/bda/tests/test_views.py index d13fcf6c..7082725c 100644 --- a/bda/tests/test_views.py +++ b/bda/tests/test_views.py @@ -8,7 +8,7 @@ from django.urls import reverse from django.utils import formats, timezone from ..models import Participant, Tirage -from .testcases import BdATestHelpers, BdAViewTestCaseMixin +from .mixins import BdATestHelpers, BdAViewTestCaseMixin User = get_user_model() diff --git a/events/tests/test_views.py b/events/tests/test_views.py index ee17128b..3e13d8cd 100644 --- a/events/tests/test_views.py +++ b/events/tests/test_views.py @@ -1,4 +1,3 @@ -import csv from unittest import mock from django.contrib.auth import get_user_model @@ -14,6 +13,7 @@ from events.models import ( OptionChoice, Registration, ) +from shared.tests.mixins import CSVResponseMixin User = get_user_model() @@ -70,7 +70,7 @@ class CSVExportAccessTest(MessagePatch, TestCase): self.assertEqual(r.status_code, 403) -class CSVExportContentTest(MessagePatch, TestCase): +class CSVExportContentTest(MessagePatch, CSVResponseMixin, TestCase): def setUp(self): super().setUp() @@ -90,13 +90,25 @@ class CSVExportContentTest(MessagePatch, TestCase): def test_simple_event(self): self.event.subscribers.set([self.u1, self.u2]) - participants = self.client.get(self.url).content.decode("utf-8") - participants = [ - line for line in csv.reader(participants.split("\n")) if line != [] - ] - self.assertEqual(len(participants), 3) - self.assertEqual(participants[1], ["toto_foo", "toto@a.b", "toto", "foo"]) - self.assertEqual(participants[2], ["titi_bar", "titi@a.b", "titi", "bar"]) + response = self.client.get(self.url) + + self.assertCSVEqual( + response, + [ + { + "username": "toto_foo", + "prénom": "toto", + "nom de famille": "foo", + "email": "toto@a.b", + }, + { + "username": "titi_bar", + "prénom": "titi", + "nom de famille": "bar", + "email": "titi@a.b", + }, + ], + ) def test_complex_event(self): registration = Registration.objects.create(event=self.event, user=self.u1) @@ -127,15 +139,18 @@ class CSVExportContentTest(MessagePatch, TestCase): field=field, registration=registration, content="hello" ) - participants = self.client.get(self.url).content.decode("utf-8") - participants = list(csv.reader(participants.split("\n"))) - toto_registration = participants[1] - - # This is not super nice, but it makes the test deterministic. - if toto_registration[5] == "f & d": - toto_registration[5] = "d & f" - - self.assertEqual( - ["toto_foo", "toto@a.b", "toto", "foo", "a", "d & f", "hello"], - toto_registration, + response = self.client.get(self.url) + self.assertCSVEqual( + response, + [ + { + "username": "toto_foo", + "prénom": "toto", + "nom de famille": "foo", + "email": "toto@a.b", + "abc": "a", + "def": "d & f", + "remarks": "hello", + } + ], ) diff --git a/events/views.py b/events/views.py index 248c4284..b47ae76f 100644 --- a/events/views.py +++ b/events/views.py @@ -38,7 +38,7 @@ def participants_csv(request, event_id): # Options all_choices = registration.options_choices.values_list("choice", flat=True) options_choices = [ - " & ".join(all_choices.filter(option__id=id)) + " & ".join(all_choices.filter(option__id=id).order_by("id")) for id in event.options.values_list("id", flat=True).order_by("id") ] row += options_choices diff --git a/gestioncof/tests/mixins.py b/gestioncof/tests/mixins.py new file mode 100644 index 00000000..5c8d767a --- /dev/null +++ b/gestioncof/tests/mixins.py @@ -0,0 +1,74 @@ +from gestioncof.models import Event +from shared.tests.mixins import ( + CSVResponseMixin, + ViewTestCaseMixin as BaseViewTestCaseMixin, +) + +from .utils import create_member, create_staff, create_user + + +class MegaHelperMixin(CSVResponseMixin): + """ + Mixin pour aider aux tests du MEGA: création de l'event et de plusieurs + inscriptions, avec options et commentaires. + """ + + def setUp(self): + super().setUp() + + u1 = create_user("u1") + u1.first_name = "first" + u1.last_name = "last" + u1.email = "user@mail.net" + u1.save() + u1.profile.phone = "0123456789" + u1.profile.departement = "Dept" + u1.profile.comments = "profile.comments" + u1.profile.save() + + u2 = create_user("u2") + u2.profile.save() + + m = Event.objects.create(title="MEGA 2018") + + cf1 = m.commentfields.create(name="Commentaires") + cf2 = m.commentfields.create(name="Comment Field 2", fieldtype="char") + + option_type = m.options.create(name="Orga ? Conscrit ?") + choice_orga = option_type.choices.create(value="Orga") + choice_conscrit = option_type.choices.create(value="Conscrit") + + mr1 = m.eventregistration_set.create(user=u1) + mr1.options.add(choice_orga) + mr1.comments.create(commentfield=cf1, content="Comment 1") + mr1.comments.create(commentfield=cf2, content="Comment 2") + + mr2 = m.eventregistration_set.create(user=u2) + mr2.options.add(choice_conscrit) + + self.u1 = u1 + self.u2 = u2 + self.m = m + self.choice_orga = choice_orga + self.choice_conscrit = choice_conscrit + + +class ViewTestCaseMixin(BaseViewTestCaseMixin): + """ + TestCase extension to ease testing of cof views. + + Most information can be found in the base parent class doc. + This class performs some changes to users management, detailed below. + + During setup, three users are created: + - 'user': a basic user without any permission, + - 'member': (profile.is_cof is True), + - 'staff': (profile.is_cof is True) && (profile.is_buro is True). + """ + + def get_users_base(self): + return { + "user": create_user("user"), + "member": create_member("member"), + "staff": create_staff("staff"), + } diff --git a/gestioncof/tests/test_views.py b/gestioncof/tests/test_views.py index f757b4c2..d522a648 100644 --- a/gestioncof/tests/test_views.py +++ b/gestioncof/tests/test_views.py @@ -1,4 +1,3 @@ -import csv import os import uuid from datetime import timedelta @@ -16,7 +15,8 @@ from django.urls import reverse from bda.models import Salle, Tirage from gestioncof.models import CalendarSubscription, Club, Event, Survey, SurveyAnswer -from gestioncof.tests.testcases import ViewTestCaseMixin +from gestioncof.tests.mixins import MegaHelperMixin, ViewTestCaseMixin +from shared.tests.mixins import CSVResponseMixin, ICalMixin, MockLDAPMixin from shared.views.autocomplete import Clipper from .utils import create_member, create_root, create_user @@ -227,7 +227,7 @@ class RegistrationFormViewTests(ViewTestCaseMixin, TestCase): auth_forbidden = [None, "user", "member"] def test_empty(self): - r = self.client.get(self.t_urls[0]) + r = self.client.get(self.reversed_urls[0]) self.assertIn("user_form", r.context) self.assertIn("profile_form", r.context) @@ -240,7 +240,7 @@ class RegistrationFormViewTests(ViewTestCaseMixin, TestCase): u.last_name = "last" u.save() - r = self.client.get(self.t_urls[1]) + r = self.client.get(self.reversed_urls[1]) self.assertIn("user_form", r.context) self.assertIn("profile_form", r.context) @@ -252,7 +252,7 @@ class RegistrationFormViewTests(ViewTestCaseMixin, TestCase): self.assertEqual(user_form["last_name"].initial, "last") def test_clipper(self): - r = self.client.get(self.t_urls[2]) + r = self.client.get(self.reversed_urls[2]) self.assertIn("user_form", r.context) self.assertIn("profile_form", r.context) @@ -267,7 +267,7 @@ class RegistrationFormViewTests(ViewTestCaseMixin, TestCase): @override_settings(LDAP_SERVER_URL="ldap_url") -class RegistrationAutocompleteViewTests(ViewTestCaseMixin, TestCase): +class RegistrationAutocompleteViewTests(MockLDAPMixin, ViewTestCaseMixin, TestCase): url_name = "cof.registration.autocomplete" url_expected = "/autocomplete/registration" @@ -462,7 +462,7 @@ class UserAutocompleteViewTests(ViewTestCaseMixin, TestCase): self.assertEqual(r.status_code, 200) -class ExportMembersViewTests(ViewTestCaseMixin, TestCase): +class ExportMembersViewTests(CSVResponseMixin, ViewTestCaseMixin, TestCase): url_name = "cof.membres_export" url_expected = "/export/members" @@ -482,70 +482,27 @@ class ExportMembersViewTests(ViewTestCaseMixin, TestCase): r = self.client.get(self.url) self.assertEqual(r.status_code, 200) - data = list(csv.reader(r.content.decode("utf-8").split("\n")[:-1])) - expected = [ + + self.assertCSVEqual( + r, [ - str(u1.pk), - "member", - "first", - "last", - "user@mail.net", - "0123456789", - "1A", - "Dept", - "normalien", + [ + str(u1.pk), + "member", + "first", + "last", + "user@mail.net", + "0123456789", + "1A", + "Dept", + "normalien", + ], + [str(u2.pk), "staff", "", "", "", "", "1A", "", "normalien"], ], - [str(u2.pk), "staff", "", "", "", "", "1A", "", "normalien"], - ] - # Sort before checking equality, the order of the output of csv.reader - # does not seem deterministic - expected.sort(key=lambda row: int(row[0])) - data.sort(key=lambda row: int(row[0])) - self.assertListEqual(data, expected) + ) -class MegaHelpers: - def setUp(self): - super().setUp() - - u1 = create_user("u1") - u1.first_name = "first" - u1.last_name = "last" - u1.email = "user@mail.net" - u1.save() - u1.profile.phone = "0123456789" - u1.profile.departement = "Dept" - u1.profile.comments = "profile.comments" - u1.profile.save() - - u2 = create_user("u2") - u2.profile.save() - - m = Event.objects.create(title="MEGA 2018") - - cf1 = m.commentfields.create(name="Commentaires") - cf2 = m.commentfields.create(name="Comment Field 2", fieldtype="char") - - option_type = m.options.create(name="Orga ? Conscrit ?") - choice_orga = option_type.choices.create(value="Orga") - choice_conscrit = option_type.choices.create(value="Conscrit") - - mr1 = m.eventregistration_set.create(user=u1) - mr1.options.add(choice_orga) - mr1.comments.create(commentfield=cf1, content="Comment 1") - mr1.comments.create(commentfield=cf2, content="Comment 2") - - mr2 = m.eventregistration_set.create(user=u2) - mr2.options.add(choice_conscrit) - - self.u1 = u1 - self.u2 = u2 - self.m = m - self.choice_orga = choice_orga - self.choice_conscrit = choice_conscrit - - -class ExportMegaViewTests(MegaHelpers, ViewTestCaseMixin, TestCase): +class ExportMegaViewTests(MegaHelperMixin, ViewTestCaseMixin, TestCase): url_name = "cof.mega_export" url_expected = "/export/mega" @@ -556,8 +513,8 @@ class ExportMegaViewTests(MegaHelpers, ViewTestCaseMixin, TestCase): r = self.client.get(self.url) self.assertEqual(r.status_code, 200) - self.assertListEqual( - self.load_from_csv_response(r), + self.assertCSVEqual( + r, [ [ "u1", @@ -574,7 +531,7 @@ class ExportMegaViewTests(MegaHelpers, ViewTestCaseMixin, TestCase): ) -class ExportMegaOrgasViewTests(MegaHelpers, ViewTestCaseMixin, TestCase): +class ExportMegaOrgasViewTests(MegaHelperMixin, ViewTestCaseMixin, TestCase): url_name = "cof.mega_export_orgas" url_expected = "/export/mega/orgas" @@ -586,8 +543,8 @@ class ExportMegaOrgasViewTests(MegaHelpers, ViewTestCaseMixin, TestCase): r = self.client.get(self.url) self.assertEqual(r.status_code, 200) - self.assertListEqual( - self.load_from_csv_response(r), + self.assertCSVEqual( + r, [ [ "u1", @@ -603,7 +560,7 @@ class ExportMegaOrgasViewTests(MegaHelpers, ViewTestCaseMixin, TestCase): ) -class ExportMegaParticipantsViewTests(MegaHelpers, ViewTestCaseMixin, TestCase): +class ExportMegaParticipantsViewTests(MegaHelperMixin, ViewTestCaseMixin, TestCase): url_name = "cof.mega_export_participants" url_expected = "/export/mega/participants" @@ -614,13 +571,12 @@ class ExportMegaParticipantsViewTests(MegaHelpers, ViewTestCaseMixin, TestCase): r = self.client.get(self.url) self.assertEqual(r.status_code, 200) - self.assertListEqual( - self.load_from_csv_response(r), - [["u2", "", "", "", "", str(self.u2.pk), "", ""]], + self.assertCSVEqual( + r, [["u2", "", "", "", "", str(self.u2.pk), "", ""]], ) -class ExportMegaRemarksViewTests(MegaHelpers, ViewTestCaseMixin, TestCase): +class ExportMegaRemarksViewTests(MegaHelperMixin, ViewTestCaseMixin, TestCase): url_name = "cof.mega_export_remarks" url_expected = "/export/mega/avecremarques" @@ -631,8 +587,8 @@ class ExportMegaRemarksViewTests(MegaHelpers, ViewTestCaseMixin, TestCase): r = self.client.get(self.url) self.assertEqual(r.status_code, 200) - self.assertListEqual( - self.load_from_csv_response(r), + self.assertCSVEqual( + r, [ [ "u1", @@ -815,7 +771,7 @@ class CalendarViewTests(ViewTestCaseMixin, TestCase): self.assertEqual(r.status_code, 200) -class CalendarICSViewTests(ViewTestCaseMixin, TestCase): +class CalendarICSViewTests(ICalMixin, ViewTestCaseMixin, TestCase): url_name = "calendar.ics" auth_user = None diff --git a/gestioncof/tests/testcases.py b/gestioncof/tests/testcases.py deleted file mode 100644 index 43f69bbc..00000000 --- a/gestioncof/tests/testcases.py +++ /dev/null @@ -1,24 +0,0 @@ -from shared.tests.testcases import ViewTestCaseMixin as BaseViewTestCaseMixin - -from .utils import create_member, create_staff, create_user - - -class ViewTestCaseMixin(BaseViewTestCaseMixin): - """ - TestCase extension to ease testing of cof views. - - Most information can be found in the base parent class doc. - This class performs some changes to users management, detailed below. - - During setup, three users are created: - - 'user': a basic user without any permission, - - 'member': (profile.is_cof is True), - - 'staff': (profile.is_cof is True) && (profile.is_buro is True). - """ - - def get_users_base(self): - return { - "user": create_user("user"), - "member": create_member("member"), - "staff": create_staff("staff"), - } diff --git a/petitscours/tests/test_petitscours_views.py b/petitscours/tests/test_views.py similarity index 98% rename from petitscours/tests/test_petitscours_views.py rename to petitscours/tests/test_views.py index 9a3cc3dc..3ef68a5a 100644 --- a/petitscours/tests/test_petitscours_views.py +++ b/petitscours/tests/test_views.py @@ -1,12 +1,11 @@ import json import os -from django.contrib import messages from django.contrib.auth import get_user_model -from django.test import Client, TestCase +from django.test import TestCase from django.urls import reverse -from gestioncof.tests.testcases import ViewTestCaseMixin +from gestioncof.tests.mixins import ViewTestCaseMixin from .utils import ( PetitCoursTestHelpers, diff --git a/shared/tests/testcases.py b/shared/tests/mixins.py similarity index 58% rename from shared/tests/testcases.py rename to shared/tests/mixins.py index 507e1361..8a00480e 100644 --- a/shared/tests/testcases.py +++ b/shared/tests/mixins.py @@ -13,6 +13,130 @@ from django.utils.functional import cached_property User = get_user_model() +class MockLDAPMixin: + """ + Mixin pour simuler un appel à un serveur LDAP (e.g., celui de l'ENS) dans des + tests unitaires. La réponse est une instance d'une classe Entry, qui simule + grossièrement l'interface de ldap3. + Cette classe patche la méthode magique `__enter__`, le code correspondant doit donc + appeler `with Connection(*args, **kwargs) as foo` pour que le test fonctionne. + """ + + def mockLDAP(self, results): + class Elt: + def __init__(self, value): + self.value = value + + class Entry: + def __init__(self, **kwargs): + for k, v in kwargs.items(): + setattr(self, k, Elt(v)) + + results_as_ldap = [Entry(uid=uid, cn=name) for uid, name in results] + + mock_connection = mock.MagicMock() + mock_connection.entries = results_as_ldap + + # Connection is used as a context manager. + mock_context_manager = mock.MagicMock() + mock_context_manager.return_value.__enter__.return_value = mock_connection + + patcher = mock.patch( + "shared.views.autocomplete.Connection", new=mock_context_manager + ) + patcher.start() + self.addCleanup(patcher.stop) + + return mock_connection + + +class CSVResponseMixin: + """ + Mixin pour manipuler des réponses données via CSV. Deux choix sont possibles: + - si `as_dict=False`, convertit le CSV en une liste de listes (une liste par ligne) + - si `as_dict=True`, convertit le CSV en une liste de dicts, avec les champs donnés + par la première ligne du CSV. + """ + + def _load_from_csv_response(self, r, as_dict=False, **reader_kwargs): + content = r.content.decode("utf-8") + + # la dernière ligne du fichier CSV est toujours vide + content = content.split("\n")[:-1] + if as_dict: + content = csv.DictReader(content, **reader_kwargs) + # en python3.7, content est une liste d'OrderedDicts + return list(map(dict, content)) + else: + content = csv.reader(content, **reader_kwargs) + return list(content) + + def assertCSVEqual(self, response, expected): + if type(expected[0]) == list: + as_dict = False + elif type(expected[0]) == dict: + as_dict = True + else: + raise AssertionError( + "Unsupported type in `assertCSVEqual`: " + "%(expected)s is not of type `list` nor `dict` !" + % {"expected": str(expected[0])} + ) + + content = self._load_from_csv_response(response, as_dict=as_dict) + self.assertCountEqual(content, expected) + + +class ICalMixin: + """ + Mixin pour manipuler des iCalendars. Permet de tester l'égalité entre + in iCal d'une part, et une liste d'évènements (représentés par des dicts) + d'autre part. + """ + + def _test_event_equal(self, event, exp): + """ + Les éléments du dict peuvent être de deux types: + - un tuple `(getter, expected_value)`, auquel cas on teste l'égalité + `getter(event[key]) == value)`; + - une variable `value` de n'importe quel autre type, auquel cas on teste + `event[key] == value`. + """ + for key, value in exp.items(): + if isinstance(value, tuple): + getter = value[0] + v = value[1] + else: + getter = lambda v: v + v = value + # dans un iCal, les fields sont en majuscules + if getter(event[key.upper()]) != v: + return False + return True + + def _find_event(self, ev, l): + for i, elt in enumerate(l): + if self._test_event_equal(ev, elt): + return i + return None + + def assertCalEqual(self, ical_content, expected): + remaining = expected.copy() + unexpected = [] + + cal = icalendar.Calendar.from_ical(ical_content) + + for ev in cal.walk("vevent"): + i_found = self._find_event(ev, remaining) + if i_found is not None: + remaining.pop(i_found) + else: + unexpected.append(ev) + + self.assertListEqual(remaining, []) + self.assertListEqual(unexpected, []) + + class TestCaseMixin: def assertForbidden(self, response): """ @@ -91,140 +215,69 @@ class TestCaseMixin: else: self.assertEqual(actual, expected) - def mockLDAP(self, results): - class Elt: - def __init__(self, value): - self.value = value - - class Entry: - def __init__(self, **kwargs): - for k, v in kwargs.items(): - setattr(self, k, Elt(v)) - - results_as_ldap = [Entry(uid=uid, cn=name) for uid, name in results] - - mock_connection = mock.MagicMock() - mock_connection.entries = results_as_ldap - - # Connection is used as a context manager. - mock_context_manager = mock.MagicMock() - mock_context_manager.return_value.__enter__.return_value = mock_connection - - patcher = mock.patch( - "shared.views.autocomplete.Connection", new=mock_context_manager - ) - patcher.start() - self.addCleanup(patcher.stop) - - return mock_connection - - def load_from_csv_response(self, r): - decoded = r.content.decode("utf-8") - return list(csv.reader(decoded.split("\n")[:-1])) - - def _test_event_equal(self, event, exp): - for k, v_desc in exp.items(): - if isinstance(v_desc, tuple): - v_getter = v_desc[0] - v = v_desc[1] - else: - v_getter = lambda v: v - v = v_desc - if v_getter(event[k.upper()]) != v: - return False - return True - - def _find_event(self, ev, l): - for i, elt in enumerate(l): - if self._test_event_equal(ev, elt): - return elt, i - return False, -1 - - def assertCalEqual(self, ical_content, expected): - remaining = expected.copy() - unexpected = [] - - cal = icalendar.Calendar.from_ical(ical_content) - - for ev in cal.walk("vevent"): - found, i_found = self._find_event(ev, remaining) - if found: - remaining.pop(i_found) - else: - unexpected.append(ev) - - self.assertListEqual(unexpected, []) - self.assertListEqual(remaining, []) - class ViewTestCaseMixin(TestCaseMixin): """ - TestCase extension to ease tests of kfet views. + Utilitaire pour automatiser certains tests sur les vues Django. + Création d'utilisateurs + ------------------------ + # Données de base + On crée dans tous les cas deux utilisateurs : un utilisateur normal "user", + et un superutilisateur "root", avec un mot de passe identique au username. - Urls concerns - ------------- + # Accès et utilisateurs supplémentaires + Les utilisateurs créés sont accessibles dans le dict `self.users`, qui associe + un label à une instance de User. - # Basic usage + Pour rajouter des utilisateurs supplémentaires (et s'assurer qu'ils sont + disponibles dans `self.users`), on peut redéfinir la fonction `get_users_extra()`, + qui doit renvoyer là aussi un dict . - Attributes: - url_name (str): Name of view under test, as given to 'reverse' - function. - url_args (list, optional): Will be given to 'reverse' call. - url_kwargs (dict, optional): Same. - url_expcted (str): What 'reverse' should return given previous - attributes. + Misc QoL + ------------------------ + Pour éviter une erreur de login (puisque les messages de Django ne sont pas + disponibles), les messages de bienvenue de GestioCOF sont patchés. + Un attribut `self.now` est fixé au démarrage, pour être donné comme valeur + de retour à un patch local de `django.utils.timezone.now`. Cela permet de + tester des dates/heures de manière robuste. - View url can then be accessed at the 'url' attribute. + Test d'URLS + ------------------------ - # Advanced usage + # Usage basique + Teste que l'URL générée par `reverse` correspond bien à l'URL théorique. + Attributs liés : + - `url_name` : nom de l'URL qui sera donné à `reverse`, + - `url_expected` : URL attendue en retour. + - (optionnels) `url_args` et `url_kwargs` : arguments de l'URL pour `reverse`. - If multiple combinations of url name, args, kwargs can be used for a view, - it is possible to define 'urls_conf' attribute. It must be a list whose - each item is a dict defining arguments for 'reverse' call ('name', 'args', - 'kwargs' keys) and its expected result ('expected' key). + # Usage avancé + On peut tester plusieurs URLs pour une même vue, en redéfinissant la fonction + `urls_conf()`. Cette fonction doit retourner une liste de dicts, avec les clés + suivantes : `name`, `args`, `kwargs`, `expected`. - The reversed urls can be accessed at the 't_urls' attribute. + # Accès aux URLs générées + Dans le cas d'usage basique, l'attribut `self.url` contient l'URL de la vue testée + (telle que renvoyée par `reverse()`). Si plusieurs URLs sont définies dans + `urls_conf()`, elles sont accessibles par la suite dans `self.reversed_urls`. + Authentification + ------------------------ + Si l'attribut `auth_user` est dans `self.users`, l'utilisateur correspondant + est authentifié avant chaque test (cela n'empêche bien sûr pas de login un autre + utilisateur à la main). - Users concerns - -------------- - - During setup, the following users are created: - - 'user': a basic user without any permission, - - 'root': a superuser, account trigramme: 200. - Their password is their username. - - One can create additionnal users with 'get_users_extra' method, or prevent - these users to be created with 'get_users_base' method. See these two - methods for further informations. - - By using 'register_user' method, these users can then be accessed at - 'users' attribute by their label. - - A user label can be given to 'auth_user' attribute. The related user is - then authenticated on self.client during test setup. Its value defaults to - 'None', meaning no user is authenticated. - - - Automated tests - --------------- - - # Url reverse - - Based on url-related attributes/properties, the test 'test_urls' checks - that expected url is returned by 'reverse' (once with basic url usage and - each for advanced usage). - - # Forbidden responses - - The 'test_forbidden' test verifies that each user, from labels of - 'auth_forbidden' attribute, can't access the url(s), i.e. response should - be a 403, or a redirect to login view. - - Tested HTTP requests are given by 'http_methods' attribute. Additional data - can be given by defining an attribute '_data'. + Test de restrictions d'accès + ------------------------ + L'utilitaire vérifie automatiquement que certains utilisateurs n'ont pas accès à la + vue. Plus spécifiquement, sont testés toutes les méthodes dans `self.http_methods` + et tous les utilisateurs dans `self.auth_forbidden`. Pour rappel, l'utilisateur + `None` sert à tester la vue sans authentification. + On peut donner des paramètres GET/POST/etc. aux tests en définissant un attribut + _data. + TODO (?): faire pareil pour vérifier les GET/POST classiques (code 200) """ url_name = None @@ -239,19 +292,13 @@ class ViewTestCaseMixin(TestCaseMixin): """ Warning: Do not forget to call super().setUp() in subclasses. """ - # 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() - # Register of User instances. self.users = {} for label, user in dict(self.users_base, **self.users_extra).items(): @@ -322,7 +369,7 @@ class ViewTestCaseMixin(TestCaseMixin): ] @property - def t_urls(self): + def reversed_urls(self): return [ reverse( url_conf["name"], @@ -335,16 +382,16 @@ class ViewTestCaseMixin(TestCaseMixin): @property def url(self): - return self.t_urls[0] + return self.reversed_urls[0] def test_urls(self): - for url, conf in zip(self.t_urls, self.urls_conf): + for url, conf in zip(self.reversed_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: + for url in self.reversed_urls: self.check_forbidden(method, url, user) def check_forbidden(self, method, url, user=None):