diff --git a/bda/tests/test_views.py b/bda/tests/test_views.py index 88dfd980..39c11c79 100644 --- a/bda/tests/test_views.py +++ b/bda/tests/test_views.py @@ -1,14 +1,93 @@ import json +from datetime import timedelta +from unittest import mock +from urllib.parse import urlencode + from django.contrib.auth.models import User from django.test import TestCase, Client from django.utils import timezone -from bda.models import Tirage, Spectacle, Salle, CategorieSpectacle +from ..models import Tirage, Spectacle, Salle, CategorieSpectacle -class TestBdAViews(TestCase): +def create_user(username, is_cof=False, is_buro=False): + user = User.objects.create_user(username=username, password=username) + user.profile.is_cof = is_cof + user.profile.is_buro = is_buro + user.profile.save() + return user + + +def user_is_cof(user): + return (user is not None) and user.profile.is_cof + + +def user_is_staff(user): + return (user is not None) and user.profile.is_buro + + +class BdATestHelpers: def setUp(self): + # Some user with different access privileges + staff = create_user(username="bda_staff", is_cof=True, is_buro=True) + staff_c = Client() + staff_c.force_login(staff) + + member = create_user(username="bda_member", is_cof=True) + member_c = Client() + member_c.force_login(member) + + other = create_user(username="bda_other") + other_c = Client() + other_c.force_login(other) + + self.client_matrix = [ + (staff, staff_c), + (member, member_c), + (other, other_c), + (None, Client()) + ] + + def require_custommails(self): + from django.core.management import call_command + call_command("syncmails") + + def check_restricted_access(self, url, + validate_user=user_is_cof, + redirect_url=None): + def craft_redirect_url(user): + if redirect_url: + return redirect_url + elif user is None: + # client is not logged in + login_url = "/login" + if url: + login_url += "?{}".format(urlencode({"next": url}, + safe="/")) + return login_url + else: + return "/" + + for (user, client) in self.client_matrix: + resp = client.get(url, follow=True) + if validate_user(user): + self.assertEqual(200, resp.status_code) + else: + self.assertRedirects(resp, craft_redirect_url(user)) + + +class TestBdAViews(BdATestHelpers, TestCase): + 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) + # Set up the helpers + super().setUp() + # Some BdA stuff self.tirage = Tirage.objects.create( title="Test tirage", appear_catalogue=True, @@ -35,46 +114,92 @@ class TestBdAViews(TestCase): ), ]) - self.bda_user = User.objects.create_user( - username="bda_user", password="bda4ever" - ) - self.bda_user.profile.is_cof = True - self.bda_user.profile.is_buro = True - self.bda_user.profile.save() + def test_bda_inscriptions(self): + # TODO: test the form + url = "/bda/inscription/{}".format(self.tirage.id) + self.check_restricted_access(url) - def bda_participants(self): - """The BdA participants views can be queried""" - client = Client() + def test_bda_places(self): + url = "/bda/places/{}".format(self.tirage.id) + self.check_restricted_access(url) + + def test_etat_places(self): + url = "/bda/etat-places/{}".format(self.tirage.id) + self.check_restricted_access(url) + + def test_perform_tirage(self): + # Only staff member can perform a tirage + url = "/bda/tirage/{}".format(self.tirage.id) + self.check_restricted_access(url, validate_user=user_is_staff) + + _, staff_c = self.client_matrix[0] + # Cannot be performed if disabled + self.tirage.enable_do_tirage = False + self.tirage.save() + resp = staff_c.get(url) + self.assertTemplateUsed(resp, "tirage-failed.html") + # Cannot be performed if registrations are still open + self.tirage.enable_do_tirage = True + self.tirage.fermeture = timezone.now() + timedelta(seconds=3600) + self.tirage.save() + resp = staff_c.get(url) + self.assertTemplateUsed(resp, "tirage-failed.html") + # Otherwise, perform the tirage + self.tirage.fermeture = timezone.now() + self.tirage.save() + resp = staff_c.get(url) + self.assertTemplateNotUsed(resp, "tirage-failed.html") + + def test_spectacles_list(self): + url = "/bda/spectacles/{}".format(self.tirage.id) + self.check_restricted_access(url, validate_user=user_is_staff) + + def test_spectacle_detail(self): show = self.tirage.spectacle_set.first() + url = "/bda/spectacles/{}/{}".format(self.tirage.id, show.id) + self.check_restricted_access(url, validate_user=user_is_staff) - client.login(self.bda_user.username, "bda4ever") - tirage_resp = client.get("/bda/spectacles/{}".format(self.tirage.id)) - show_resp = client.get( - "/bda/spectacles/{}/{}".format(self.tirage.id, show.id) - ) - reminder_url = "/bda/mails-rappel/{}".format(show.id) - reminder_get_resp = client.get(reminder_url) - reminder_post_resp = client.post(reminder_url) - self.assertEqual(200, tirage_resp.status_code) - self.assertEqual(200, show_resp.status_code) - self.assertEqual(200, reminder_get_resp.status_code) - self.assertEqual(200, reminder_post_resp.status_code) + def test_tirage_unpaid(self): + url = "/bda/spectacles/unpaid/{}".format(self.tirage.id) + self.check_restricted_access(url, validate_user=user_is_staff) - def test_catalogue(self): - """Test the catalogue JSON API""" - client = Client() + def test_send_reminders(self): + self.require_custommails() + # Just get the page + show = self.tirage.spectacle_set.first() + url = "/bda/mails-rappel/{}".format(show.id) + self.check_restricted_access(url, validate_user=user_is_staff) + # Actually send the reminder emails + _, staff_c = self.client_matrix[0] + resp = staff_c.post(url) + self.assertEqual(200, resp.status_code) + # TODO: check that emails are sent - # The `list` hook - resp = client.get("/bda/catalogue/list") + def test_catalogue_api(self): + url_list = "/bda/catalogue/list" + url_details = "/bda/catalogue/details?id={}".format(self.tirage.id) + url_descriptions = "/bda/catalogue/descriptions?id={}".format(self.tirage.id) + + # Anyone can get + def anyone_can_get(url): + self.check_restricted_access(url, validate_user=lambda user: True) + + anyone_can_get(url_list) + anyone_can_get(url_details) + anyone_can_get(url_descriptions) + + # The resulting JSON contains the information + _, client = self.client_matrix[0] + + # List + resp = client.get(url_list) self.assertJSONEqual( resp.content.decode("utf-8"), [{"id": self.tirage.id, "title": self.tirage.title}] ) - # The `details` hook - resp = client.get( - "/bda/catalogue/details?id={}".format(self.tirage.id) - ) + # Details + resp = client.get(url_details) self.assertJSONEqual( resp.content.decode("utf-8"), { @@ -89,10 +214,8 @@ class TestBdAViews(TestCase): } ) - # The `descriptions` hook - resp = client.get( - "/bda/catalogue/descriptions?id={}".format(self.tirage.id) - ) + # Descriptions + resp = client.get(url_descriptions) raw = resp.content.decode("utf-8") try: results = json.loads(raw) @@ -103,3 +226,8 @@ class TestBdAViews(TestCase): {(s["title"], s["price"], s["slots"]) for s in results}, {("foo", 0, 42), ("bar", 1, 142), ("baz", 2, 242)} ) + + +class TestBdaRevente: + pass + # TODO diff --git a/gestioncof/management/commands/syncmails.py b/gestioncof/management/commands/syncmails.py index 689da716..8f302186 100644 --- a/gestioncof/management/commands/syncmails.py +++ b/gestioncof/management/commands/syncmails.py @@ -10,6 +10,75 @@ from django.core.management.base import BaseCommand from django.contrib.contenttypes.models import ContentType +DATA_LOCATION = os.path.join(os.path.dirname(__file__), "..", "data", "custommail.json") + + +def dummy_log(__): + pass + + +# XXX. this should probably be in the custommail package +def load_from_file(log=dummy_log, verbosity=1): + with open(DATA_LOCATION, 'r') as jsonfile: + mail_data = json.load(jsonfile) + + # On se souvient à quel objet correspond quel pk du json + assoc = {'types': {}, 'mails': {}} + status = {'synced': 0, 'unchanged': 0} + + for obj in mail_data: + fields = obj['fields'] + + # Pour les trois types d'objets : + # - On récupère les objets référencés par les clefs étrangères + # - On crée l'objet si nécessaire + # - On le stocke éventuellement dans les deux dictionnaires définis + # plus haut + + # Variable types + if obj['model'] == 'custommail.variabletype': + fields['inner1'] = assoc['types'].get(fields['inner1']) + fields['inner2'] = assoc['types'].get(fields['inner2']) + if fields['kind'] == 'model': + fields['content_type'] = ( + ContentType.objects + .get_by_natural_key(*fields['content_type']) + ) + var_type, _ = Type.objects.get_or_create(**fields) + assoc['types'][obj['pk']] = var_type + + # Custom mails + if obj['model'] == 'custommail.custommail': + mail = None + try: + mail = CustomMail.objects.get(shortname=fields['shortname']) + status['unchanged'] += 1 + except CustomMail.DoesNotExist: + mail = CustomMail.objects.create(**fields) + status['synced'] += 1 + if verbosity: + log('SYNCED {:s}'.format(fields['shortname'])) + assoc['mails'][obj['pk']] = mail + + # Variables + if obj['model'] == 'custommail.custommailvariable': + fields['custommail'] = assoc['mails'].get(fields['custommail']) + fields['type'] = assoc['types'].get(fields['type']) + try: + Variable.objects.get( + custommail=fields['custommail'], + name=fields['name'] + ) + except Variable.DoesNotExist: + Variable.objects.create(**fields) + + if verbosity: + log( + '{synced:d} mails synchronized {unchanged:d} unchanged' + .format(**status) + ) + + class Command(BaseCommand): help = ("Va chercher les données mails de GestioCOF stocké au format json " "dans /gestioncof/management/data/custommails.json. Le format des " @@ -21,67 +90,4 @@ class Command(BaseCommand): "remplacer par le nouveau résultat de la commande précédente.") def handle(self, *args, **options): - path = os.path.join( - os.path.dirname(os.path.dirname(__file__)), - 'data', 'custommail.json') - with open(path, 'r') as jsonfile: - mail_data = json.load(jsonfile) - - # On se souvient à quel objet correspond quel pk du json - assoc = {'types': {}, 'mails': {}} - status = {'synced': 0, 'unchanged': 0} - - for obj in mail_data: - fields = obj['fields'] - - # Pour les trois types d'objets : - # - On récupère les objets référencés par les clefs étrangères - # - On crée l'objet si nécessaire - # - On le stocke éventuellement dans les deux dictionnaires définis - # plus haut - - # Variable types - if obj['model'] == 'custommail.variabletype': - fields['inner1'] = assoc['types'].get(fields['inner1']) - fields['inner2'] = assoc['types'].get(fields['inner2']) - if fields['kind'] == 'model': - fields['content_type'] = ( - ContentType.objects - .get_by_natural_key(*fields['content_type']) - ) - var_type, _ = Type.objects.get_or_create(**fields) - assoc['types'][obj['pk']] = var_type - - # Custom mails - if obj['model'] == 'custommail.custommail': - mail = None - try: - mail = CustomMail.objects.get( - shortname=fields['shortname']) - status['unchanged'] += 1 - except CustomMail.DoesNotExist: - mail = CustomMail.objects.create(**fields) - status['synced'] += 1 - if options['verbosity']: - self.stdout.write( - 'SYNCED {:s}'.format(fields['shortname'])) - assoc['mails'][obj['pk']] = mail - - # Variables - if obj['model'] == 'custommail.custommailvariable': - fields['custommail'] = assoc['mails'].get(fields['custommail']) - fields['type'] = assoc['types'].get(fields['type']) - try: - Variable.objects.get( - custommail=fields['custommail'], - name=fields['name'] - ) - except Variable.DoesNotExist: - Variable.objects.create(**fields) - - if options['verbosity']: - # C'est agréable d'avoir le résultat affiché - self.stdout.write( - '{synced:d} mails synchronized {unchanged:d} unchanged' - .format(**status) - ) + load_from_file(log=self.stdout.write)