diff --git a/gestioncof/autocomplete.py b/gestioncof/autocomplete.py index 1d60cd78..d6483869 100644 --- a/gestioncof/autocomplete.py +++ b/gestioncof/autocomplete.py @@ -19,6 +19,13 @@ class Clipper(object): self.clipper = clipper self.fullname = fullname + def __str__(self): + return '{} ({})'.format(self.clipper, self.fullname) + + def __eq__(self, other): + return ( + self.clipper == other.clipper and self.fullname == other.fullname) + @buro_required def autocomplete(request): diff --git a/gestioncof/tests/test_views.py b/gestioncof/tests/test_views.py index f6dd7eb9..1ab6a5f4 100644 --- a/gestioncof/tests/test_views.py +++ b/gestioncof/tests/test_views.py @@ -3,19 +3,344 @@ import uuid from datetime import timedelta from django.contrib import messages +from django.contrib.auth import get_user_model from django.contrib.messages.api import get_messages from django.contrib.messages.storage.base import Message -from django.test import Client, TestCase +from django.core import mail +from django.core.management import call_command +from django.test import Client, TestCase, override_settings from django.urls import reverse from bda.models import Salle, Tirage +from gestioncof.autocomplete import Clipper from gestioncof.models import ( CalendarSubscription, Club, Event, Survey, SurveyAnswer ) from gestioncof.tests.testcases import ViewTestCaseMixin +from custommail.models import CustomMail + from .utils import create_member, create_root, create_user +User = get_user_model() + + +class RegistrationViewTests(ViewTestCaseMixin, TestCase): + url_name = 'registration' + url_expected = '/registration' + + http_methods = ['GET', 'POST'] + + auth_user = 'staff' + auth_forbidden = [None, 'user', 'member'] + + def requires_mails(self): + call_command('syncmails', verbosity=0) + + def test_get(self): + r = self.client.get(self.url) + self.assertEqual(r.status_code, 200) + + @property + def _minimal_data(self): + return { + 'first_name': '', + 'last_name': '', + 'email': '', + + # 'is_cof': '1', + 'login_clipper': '', + 'phone': '', + 'occupation': '1A', + 'departement': '', + 'type_cotiz': 'normalien', + 'comments': '', + + # 'user_exists': '1', + + 'events-TOTAL_FORMS': '0', + 'events-INITIAL_FORMS': '0', + 'events-MIN_NUM_FORMS': '0', + 'events-MAX_NUM_FORMS': '1000', + } + + def test_post_new(self): + self.requires_mails() + + r = self.client.post(self.url, dict(self._minimal_data, **{ + 'username': 'username', + 'first_name': 'first', + 'last_name': 'last', + 'email': 'username@mail.net', + 'is_cof': '1', + })) + + self.assertEqual(r.status_code, 200) + u = User.objects.get(username='username') + expected_message = Message(messages.SUCCESS, ( + "L'inscription de first last (username@mail.net) a été " + "enregistrée avec succès.\n" + "Il est désormais membre du COF n°{} !" + .format(u.pk) + )) + self.assertIn(expected_message, get_messages(r.wsgi_request)) + + self.assertEqual(u.first_name, 'first') + self.assertEqual(u.last_name, 'last') + self.assertEqual(u.email, 'username@mail.net') + + def test_post_edit(self): + self.requires_mails() + u = self.users['user'] + + r = self.client.post(self.url, dict(self._minimal_data, **{ + 'username': 'user', + 'first_name': 'first', + 'last_name': 'last', + 'email': 'user@mail.net', + 'is_cof': '1', + 'user_exists': '1', + })) + + self.assertEqual(r.status_code, 200) + u.refresh_from_db() + expected_message = Message(messages.SUCCESS, ( + "L'inscription de first last (user@mail.net) a été " + "enregistrée avec succès.\n" + "Il est désormais membre du COF n°{} !" + .format(u.pk) + )) + self.assertIn(expected_message, get_messages(r.wsgi_request)) + + self.assertEqual(u.first_name, 'first') + self.assertEqual(u.last_name, 'last') + self.assertEqual(u.email, 'user@mail.net') + + def _test_mail_welcome(self, was_cof, is_cof, expect_mail): + self.requires_mails() + u = self.users['member'] if was_cof else self.users['user'] + + data = dict(self._minimal_data, **{ + 'username': u.username, + 'email': 'user@mail.net', + 'user_exists': '1', + }) + if is_cof: + data['is_cof'] = '1' + self.client.post(self.url, data) + + u.refresh_from_db() + + def _is_sent(): + cm = CustomMail.objects.get(shortname='welcome') + welcome_msg = cm.get_message({'member': u}) + for m in mail.outbox: + if m.subject == welcome_msg.subject: + return True + return False + + self.assertEqual(_is_sent(), expect_mail) + + def test_mail_welcome_0(self): + self._test_mail_welcome(was_cof=False, is_cof=False, expect_mail=False) + + def test_mail_welcome_1(self): + self._test_mail_welcome(was_cof=False, is_cof=True, expect_mail=True) + + def test_mail_welcome_2(self): + self._test_mail_welcome(was_cof=True, is_cof=False, expect_mail=False) + + def test_mail_welcome_3(self): + self._test_mail_welcome(was_cof=True, is_cof=True, expect_mail=False) + + def test_events(self): + e = Event.objects.create() + + cf1 = e.commentfields.create(name='Comment Field 1') + cf2 = e.commentfields.create( + name='Comment Field 2', fieldtype='char', + ) + + o1 = e.options.create(name='Option 1') + o2 = e.options.create(name='Option 2', multi_choices=True) + + oc1 = o1.choices.create(value='O1 - Choice 1') + o1.choices.create(value='O1 - Choice 2') + oc3 = o2.choices.create(value='O2 - Choice 1') + o2.choices.create(value='O2 - Choice 2') + + self.client.post(self.url, dict(self._minimal_data, **{ + 'username': 'user', + 'user_exists': '1', + 'events-TOTAL_FORMS': '1', + 'events-INITIAL_FORMS': '0', + 'events-MIN_NUM_FORMS': '0', + 'events-MAX_NUM_FORMS': '1000', + 'events-0-status': 'paid', + 'events-0-option_{}'.format(o1.pk): [str(oc1.pk)], + 'events-0-option_{}'.format(o2.pk): [str(oc3.pk)], + 'events-0-comment_{}'.format(cf1.pk): 'comment 1', + 'events-0-comment_{}'.format(cf2.pk): '', + })) + + er = e.eventregistration_set.get(user=self.users['user']) + self.assertQuerysetEqual( + er.options.all(), map(repr, [oc1, oc3]), + ordered=False, + ) + self.assertCountEqual(er.comments.values_list('content', flat=True), [ + 'comment 1', + ]) + + +class RegistrationFormViewTests(ViewTestCaseMixin, TestCase): + urls_conf = [ + { + 'name': 'empty-registration', + 'expected': '/registration/empty', + }, + { + 'name': 'user-registration', + 'kwargs': {'username': 'user'}, + 'expected': '/registration/user/user', + }, + { + 'name': 'clipper-registration', + 'kwargs': { + 'login_clipper': 'uid', + 'fullname': 'First Last1 Last2', + }, + 'expected': '/registration/clipper/uid/First%20Last1%20Last2', + }, + ] + + auth_user = 'staff' + auth_forbidden = [None, 'user', 'member'] + + def test_empty(self): + r = self.client.get(self.t_urls[0]) + + self.assertIn('user_form', r.context) + self.assertIn('profile_form', r.context) + self.assertIn('event_formset', r.context) + self.assertIn('clubs_form', r.context) + + def test_username(self): + u = self.users['user'] + u.first_name = 'first' + u.last_name = 'last' + u.save() + + r = self.client.get(self.t_urls[1]) + + self.assertIn('user_form', r.context) + self.assertIn('profile_form', r.context) + self.assertIn('event_formset', r.context) + self.assertIn('clubs_form', r.context) + user_form = r.context['user_form'] + self.assertEqual(user_form['username'].initial, 'user') + self.assertEqual(user_form['first_name'].initial, 'first') + self.assertEqual(user_form['last_name'].initial, 'last') + + def test_clipper(self): + r = self.client.get(self.t_urls[2]) + + self.assertIn('user_form', r.context) + self.assertIn('profile_form', r.context) + self.assertIn('event_formset', r.context) + self.assertIn('clubs_form', r.context) + user_form = r.context['user_form'] + profile_form = r.context['profile_form'] + self.assertEqual(user_form['first_name'].initial, 'First') + self.assertEqual(user_form['last_name'].initial, 'Last1 Last2') + self.assertEqual(user_form['email'].initial, 'uid@clipper.ens.fr') + self.assertEqual(profile_form['login_clipper'].initial, 'uid') + + +@override_settings(LDAP_SERVER_URL='ldap_url') +class RegistrationAutocompleteViewTests(ViewTestCaseMixin, TestCase): + url_name = 'cof.registration.autocomplete' + url_expected = '/autocomplete/registration' + + auth_user = 'staff' + auth_forbidden = [None, 'user', 'member'] + + def setUp(self): + super().setUp() + + self.u1 = create_user('uu_u1', attrs={ + 'first_name': 'abc', 'last_name': 'xyz', + }) + self.u2 = create_user('uu_u2', attrs={ + 'first_name': 'wyz', 'last_name': 'abd', + }) + self.m1 = create_member('uu_m1', attrs={ + 'first_name': 'ebd', 'last_name': 'wyv', + }) + + self.mockLDAP([]) + + def _test( + self, query, expected_users, expected_members, expected_clippers, + ): + r = self.client.get(self.url, {'q': query}) + + self.assertEqual(r.status_code, 200) + + self.assertQuerysetEqual( + r.context['users'], map(repr, expected_users), + ordered=False, + ) + self.assertQuerysetEqual( + r.context['members'], + map(lambda u: repr(u.profile), expected_members), + ordered=False, + ) + self.assertCountEqual( + map(str, r.context.get('clippers', [])), + map(str, expected_clippers), + ) + + def test_username(self): + self._test('uu', [self.u1, self.u2], [self.m1], []) + + def test_firstname(self): + self._test('ab', [self.u1, self.u2], [], []) + + def test_lastname(self): + self._test('wy', [self.u2], [self.m1], []) + + def test_multi_query(self): + self._test('wy bd', [self.u2], [self.m1], []) + + def test_clipper(self): + mock_ldap = self.mockLDAP([('uid', 'first last')]) + + self._test('aa bb', [], [], [Clipper('uid', 'first last')]) + + mock_ldap.search.assert_called_once_with( + 'dc=spi,dc=ens,dc=fr', + '(&(|(cn=*aa*)(uid=*aa*))(|(cn=*bb*)(uid=*bb*)))', + attributes=['uid', 'cn'], + ) + + def test_clipper_escaped(self): + mock_ldap = self.mockLDAP([]) + + self._test('; & | (', [], [], []) + + mock_ldap.search.assert_not_called() + + def test_clipper_no_duplicate(self): + self.mockLDAP([('uid', 'uu_u1')]) + + self._test('uu u1', [self.u1], [], [Clipper('uid', 'uu_u1')]) + + self.u1.profile.login_clipper = 'uid' + self.u1.profile.save() + + self._test('uu u1', [self.u1], [], []) + class HomeViewTests(ViewTestCaseMixin, TestCase): url_name = 'home' @@ -458,9 +783,10 @@ class CalendarViewTests(ViewTestCaseMixin, TestCase): fermeture=self.now, active=True, ) - l = Salle.objects.create() + location = Salle.objects.create() s = t.spectacle_set.create( - date=self.now, price=3.5, slots=20, location=l, listing=True) + date=self.now, price=3.5, slots=20, location=location, + listing=True) r = self.client.post(self.url, {'other_shows': [str(s.pk)]}) @@ -491,17 +817,17 @@ class CalendarICSViewTests(ViewTestCaseMixin, TestCase): fermeture=self.now, active=True, ) - l = Salle.objects.create(name='Location') + location = Salle.objects.create(name='Location') self.s1 = self.t.spectacle_set.create( - price=1, slots=10, location=l, listing=True, + price=1, slots=10, location=location, listing=True, title='Spectacle 1', date=self.now + timedelta(days=1), ) self.s2 = self.t.spectacle_set.create( - price=2, slots=20, location=l, listing=True, + price=2, slots=20, location=location, listing=True, title='Spectacle 2', date=self.now + timedelta(days=2), ) self.s3 = self.t.spectacle_set.create( - price=3, slots=30, location=l, listing=True, + price=3, slots=30, location=location, listing=True, title='Spectacle 3', date=self.now + timedelta(days=3), ) diff --git a/shared/tests/testcases.py b/shared/tests/testcases.py index 6967d9b7..91bd9d38 100644 --- a/shared/tests/testcases.py +++ b/shared/tests/testcases.py @@ -95,6 +95,38 @@ 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( + 'gestioncof.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]))