Sépare un gros fourre-tout en plus petits mixins

This commit is contained in:
Ludovic Stephan 2020-05-10 23:54:21 +02:00
parent f642b218d0
commit bbe831a226
3 changed files with 112 additions and 67 deletions

View file

@ -17,6 +17,7 @@ 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 shared.tests.testcases import ICalMixin, MockLDAPMixin
from shared.views.autocomplete import Clipper
from .utils import create_member, create_root, create_user
@ -267,7 +268,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"
@ -815,7 +816,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

View file

@ -1,4 +1,7 @@
from shared.tests.testcases import ViewTestCaseMixin as BaseViewTestCaseMixin
from shared.tests.testcases import (
CSVResponseMixin,
ViewTestCaseMixin as BaseViewTestCaseMixin,
)
from .utils import create_member, create_staff, create_user

View file

@ -13,6 +13,111 @@ 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:
reader_class = csv.DictReader
else:
reader_class = csv.reader
return list(reader_class(content, **reader_kwargs))
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 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)
class TestCaseMixin:
def assertForbidden(self, response):
"""
@ -91,70 +196,6 @@ 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):