From 9734b51f53b57debedbb6f5cdcd6d3f93e2e48ae Mon Sep 17 00:00:00 2001 From: Emmy D'Anello Date: Sun, 9 Apr 2023 00:50:47 +0200 Subject: [PATCH] Test draw application Signed-off-by: Emmy D'Anello --- draw/consumers.py | 7 - draw/models.py | 9 + draw/tests.py | 760 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 769 insertions(+), 7 deletions(-) diff --git a/draw/consumers.py b/draw/consumers.py index f4db5a3..e3b7af1 100644 --- a/draw/consumers.py +++ b/draw/consumers.py @@ -36,13 +36,6 @@ class DrawConsumer(AsyncJsonWebsocketConsumer): """ This consumer manages the websocket of the draw interface. """ - def __int__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.tournament_id = None - self.tournament = None - self.participations = None - self.registration = None - async def connect(self) -> None: """ This function is called when a new websocket is trying to connect to the server. diff --git a/draw/models.py b/draw/models.py index bf35f63..af5f9b2 100644 --- a/draw/models.py +++ b/draw/models.py @@ -206,6 +206,9 @@ class Round(models.Model): help_text=_("The current pool where teams select their problems."), ) + def get_absolute_url(self): + return reverse_lazy('draw:index') + f'#{slugify(self.draw.tournament.name)}' + @property def team_draws(self) -> QuerySet["TeamDraw"]: """ @@ -278,6 +281,9 @@ class Pool(models.Model): help_text=_("The full pool instance."), ) + def get_absolute_url(self): + return reverse_lazy('draw:index') + f'#{slugify(self.round.draw.tournament.name)}' + @property def team_draws(self) -> QuerySet["TeamDraw"]: """ @@ -483,6 +489,9 @@ class TeamDraw(models.Model): verbose_name=_('rejected problems'), ) + def get_absolute_url(self): + return reverse_lazy('draw:index') + f'#{slugify(self.round.draw.tournament.name)}' + @property def last_dice(self): """ diff --git a/draw/tests.py b/draw/tests.py index c1c661c..3b45cde 100644 --- a/draw/tests.py +++ b/draw/tests.py @@ -1,2 +1,762 @@ # Copyright (C) 2023 by Animath # SPDX-License-Identifier: GPL-3.0-or-later + +from random import shuffle + +from asgiref.sync import sync_to_async +from channels.auth import AuthMiddlewareStack +from channels.routing import URLRouter +from channels.testing import WebsocketCommunicator +from django.conf import settings +from django.contrib.auth.models import User +from django.contrib.contenttypes.models import ContentType +from django.contrib.sites.models import Site +from django.test import TestCase +from django.urls import reverse +from participation.models import Team, Tournament + +from . import routing +from .models import Draw, Pool, Round, TeamDraw + + +class TestDraw(TestCase): + def setUp(self): + self.superuser = User.objects.create_superuser( + username="admin", + email="admin@example.com", + password="toto1234", + ) + + self.tournament = Tournament.objects.create( + name="Test", + ) + self.teams = [] + for i in range(12): + t = Team.objects.create( + name=f"Team {i + 1}", + trigram=3 * chr(65 + i), + ) + t.participation.tournament = self.tournament + t.participation.valid = True + t.participation.save() + self.teams.append(t) + shuffle(self.teams) + + async def test_draw(self): # noqa: C901 + """ + Simulate a full draw operation. + """ + await sync_to_async(self.async_client.force_login)(self.superuser) + + resp = await self.async_client.get(reverse('draw:index')) + self.assertEqual(resp.status_code, 200) + + # Connect to Websocket + headers = [(b'cookie', self.async_client.cookies.output(header='', sep='; ').encode())] + communicator = WebsocketCommunicator(AuthMiddlewareStack(URLRouter(routing.websocket_urlpatterns)), + f"/ws/draw/{self.tournament.id}/", + headers) + connected, subprotocol = await communicator.connect() + self.assertTrue(connected) + + # Define language + await communicator.send_json_to({'type': 'set_language', 'language': 'en'}) + + # Ensure that Draw has not started + self.assertFalse(await Draw.objects.filter(tournament=self.tournament).aexists()) + + # Must be an error since 1+1+1 != 12 + await communicator.send_json_to({'type': 'start_draw', 'fmt': '1+1+1'}) + resp = await communicator.receive_json_from() + self.assertEqual(resp['type'], 'alert') + self.assertEqual(resp['alert_type'], 'danger') + self.assertEqual(resp['message'], "The sum must be equal to the number of teams: expected 12, got 3") + self.assertFalse(await Draw.objects.filter(tournament=self.tournament).aexists()) + + # Now start the draw + await communicator.send_json_to({'type': 'start_draw', 'fmt': '3+4+5'}) + + # Receive data after the start + self.assertEqual((await communicator.receive_json_from())['type'], 'alert') + self.assertEqual(await communicator.receive_json_from(), + {'type': 'set_poules', 'round': 1, + 'poules': [{'letter': 'A', 'teams': []}, + {'letter': 'B', 'teams': []}, + {'letter': 'C', 'teams': []}]}) + self.assertEqual(await communicator.receive_json_from(), + {'type': 'set_poules', 'round': 2, + 'poules': [{'letter': 'A', 'teams': []}, + {'letter': 'B', 'teams': []}, + {'letter': 'C', 'teams': []}]}) + self.assertEqual(await communicator.receive_json_from(), {'type': 'dice_visibility', 'visible': True}) + self.assertEqual((await communicator.receive_json_from())['type'], 'alert') + self.assertEqual(await communicator.receive_json_from(), + {'type': 'draw_start', 'fmt': [5, 4, 3], + 'trigrams': ['AAA', 'BBB', 'CCC', 'DDD', 'EEE', 'FFF', + 'GGG', 'HHH', 'III', 'JJJ', 'KKK', 'LLL']}) + self.assertEqual((await communicator.receive_json_from())['type'], 'set_info') + self.assertEqual(await communicator.receive_json_from(), + {'type': 'set_active', 'round': 1, 'poule': None, 'team': None}) + self.assertEqual((await communicator.receive_json_from())['type'], 'notification') + + # Ensure that now tournament has started + self.assertTrue(await Draw.objects.filter(tournament=self.tournament).aexists()) + + # Render page + resp = await self.async_client.get(reverse('draw:index')) + self.assertEqual(resp.status_code, 200) + + # Try to relaunch the draw + await communicator.send_json_to({'type': 'start_draw', 'fmt': '3+4+5'}) + resp = await communicator.receive_json_from() + self.assertEqual(resp['type'], 'alert') + self.assertEqual(resp['alert_type'], 'danger') + self.assertEqual(resp['message'], "The draw is already started.") + + draw: Draw = await Draw.objects.prefetch_related( + 'current_round__current_pool__current_team__participation__team').aget(tournament=self.tournament) + r: Round = draw.current_round + + for i, team in enumerate(self.teams): + # Launch a new dice + await communicator.send_json_to({'type': "dice", 'trigram': team.trigram}) + resp = await communicator.receive_json_from() + self.assertEqual(resp['type'], "dice") + self.assertEqual(resp['team'], team.trigram) + self.assertGreaterEqual(resp['result'], 1) + self.assertLessEqual(resp['result'], 100) + td: TeamDraw = await r.teamdraw_set.aget(participation=team.participation) + if i != len(self.teams) - 1: + self.assertEqual(resp['result'], td.passage_dice) + + # Try to relaunch the dice + await communicator.send_json_to({'type': "dice", 'trigram': team.trigram}) + resp = await communicator.receive_json_from() + self.assertEqual(resp['type'], 'alert') + self.assertEqual(resp['message'], "You've already launched the dice.") + + # Force exactly one duplicate + await td.arefresh_from_db() + td.passage_dice = 101 + i if i != 2 else 101 + await td.asave() + + # Manage duplicates + while dup_count := await r.teamdraw_set.filter(passage_dice__isnull=True).acount(): + for i in range(dup_count): + # Dice + resp = await communicator.receive_json_from() + self.assertEqual(resp['type'], 'dice') + self.assertIsNone(resp['result']) + # Alert + self.assertEqual((await communicator.receive_json_from())['type'], 'alert') + + for i in range(dup_count): + await communicator.send_json_to({'type': "dice", 'trigram': None}) + await communicator.receive_json_from() + + # Reset dices + for _i in range(12): + resp = await communicator.receive_json_from() + self.assertEqual(resp['type'], 'dice') + self.assertIsNone(resp['result']) + + # Hide and re-display the dice + self.assertEqual(await communicator.receive_json_from(), {'type': 'dice_visibility', 'visible': False}) + self.assertEqual(await communicator.receive_json_from(), {'type': 'dice_visibility', 'visible': True}) + + # Set pools for the two rounds + self.assertEqual((await communicator.receive_json_from())['type'], 'set_poules') + self.assertEqual((await communicator.receive_json_from())['type'], 'set_poules') + + self.assertEqual((await communicator.receive_json_from())['type'], 'set_info') + + # Manage the first pool + self.assertEqual(await communicator.receive_json_from(), + {'type': 'set_active', 'round': 1, 'poule': 'A', 'team': None}) + r: Round = await Round.objects.prefetch_related('current_pool__current_team__participation__team')\ + .aget(number=1, draw=draw) + p = r.current_pool + self.assertEqual(p.letter, 1) + self.assertEqual(p.size, 5) + self.assertEqual(await p.teamdraw_set.acount(), 5) + self.assertEqual(p.current_team, None) + + # Render page + resp = await self.async_client.get(reverse('draw:index')) + self.assertEqual(resp.status_code, 200) + + i = 0 + async for td in p.teamdraw_set.prefetch_related('participation__team').all(): + # Launch a new dice + await communicator.send_json_to({'type': "dice", 'trigram': td.participation.team.trigram}) + resp = await communicator.receive_json_from() + self.assertEqual(resp['type'], "dice") + trigram = td.participation.team.trigram + self.assertEqual(resp['team'], trigram) + self.assertGreaterEqual(resp['result'], 1) + self.assertLessEqual(resp['result'], 100) + if i != p.size - 1: + await td.arefresh_from_db() + self.assertEqual(resp['result'], td.choice_dice) + + # Try to relaunch the dice + await communicator.send_json_to({'type': "dice", 'trigram': trigram}) + resp = await communicator.receive_json_from() + self.assertEqual(resp['type'], 'alert') + self.assertEqual(resp['message'], "You've already launched the dice.") + + # Force exactly one duplicate + await td.arefresh_from_db() + td.passage_dice = 101 + i if i != 1 else 101 + await td.asave() + i += 1 + + # Manage duplicates + while dup_count := await p.teamdraw_set.filter(choice_dice__isnull=True).acount(): + for i in range(dup_count): + # Dice + resp = await communicator.receive_json_from() + self.assertEqual(resp['type'], 'dice') + self.assertIsNone(resp['result']) + # Alert + self.assertEqual((await communicator.receive_json_from())['type'], 'alert') + + for i in range(dup_count): + await communicator.send_json_to({'type': "dice", 'trigram': None}) + await communicator.receive_json_from() + + self.assertEqual((await communicator.receive_json_from())['type'], 'set_info') + + # Check current pool + p: Pool = await Pool.objects.prefetch_related('current_team__participation__team').aget(round=r, letter=1) + td = p.current_team + trigram = td.participation.team.trigram + self.assertEqual(td.choose_index, 0) + self.assertEqual(await communicator.receive_json_from(), {'type': 'set_active', 'round': 1, 'poule': 'A', + 'team': td.participation.team.trigram}) + # Dice is hidden for everyone first + self.assertEqual(await communicator.receive_json_from(), {'type': 'dice_visibility', 'visible': False}) + # The draw box is displayed for the current team and for volunteers + self.assertEqual(await communicator.receive_json_from(), {'type': 'box_visibility', 'visible': True}) + + # Render page + resp = await self.async_client.get(reverse('draw:index')) + self.assertEqual(resp.status_code, 200) + + # Try to launch a dice while it is not the time + await communicator.send_json_to({'type': 'dice', 'trigram': None}) + resp = await communicator.receive_json_from() + self.assertEqual(resp['type'], 'alert') + self.assertEqual(resp['message'], "This is not the time for this.") + + # Draw a problem + await communicator.send_json_to({'type': 'draw_problem'}) + self.assertEqual(await communicator.receive_json_from(), {'type': 'box_visibility', 'visible': False}) + self.assertEqual(await communicator.receive_json_from(), {'type': 'buttons_visibility', 'visible': True}) + self.assertEqual((await communicator.receive_json_from())['type'], 'set_info') + + td: TeamDraw = await TeamDraw.objects.prefetch_related('participation__team').aget(pk=td.pk) + purposed = td.purposed + self.assertIsNotNone(td.purposed) + self.assertIn(td.purposed, range(1, len(settings.PROBLEMS) + 1)) + + # Render page + resp = await self.async_client.get(reverse('draw:index')) + self.assertEqual(resp.status_code, 200) + + # Try to redraw a problem while it is not the time + await communicator.send_json_to({'type': 'draw_problem'}) + resp = await communicator.receive_json_from() + self.assertEqual(resp['type'], 'alert') + self.assertEqual(resp['message'], "This is not the time for this.") + + # Reject the first problem + await communicator.send_json_to({'type': 'reject'}) + self.assertEqual(await communicator.receive_json_from(), {'type': 'buttons_visibility', 'visible': False}) + self.assertEqual(await communicator.receive_json_from(), + {'type': 'reject_problem', 'round': 1, 'team': trigram, 'rejected': [purposed]}) + self.assertEqual(await communicator.receive_json_from(), {'type': 'box_visibility', 'visible': True}) + self.assertEqual((await communicator.receive_json_from())['type'], 'set_info') + td: TeamDraw = await TeamDraw.objects.prefetch_related('participation__team').aget(pk=td.pk) + self.assertIsNone(td.purposed) + self.assertEqual(td.rejected, [purposed]) + + for i in range(4): + # Next team + p: Pool = await Pool.objects.prefetch_related('current_team__participation__team').aget(round=r, letter=1) + td = p.current_team + trigram = td.participation.team.trigram + self.assertEqual(td.choose_index, i + 1) + self.assertEqual(await communicator.receive_json_from(), {'type': 'set_active', 'round': 1, 'poule': 'A', + 'team': trigram}) + + # Render page + resp = await self.async_client.get(reverse('draw:index')) + self.assertEqual(resp.status_code, 200) + + # Draw a problem + await communicator.send_json_to({'type': 'draw_problem'}) + self.assertEqual(await communicator.receive_json_from(), {'type': 'box_visibility', 'visible': False}) + self.assertEqual(await communicator.receive_json_from(), {'type': 'buttons_visibility', 'visible': True}) + self.assertEqual((await communicator.receive_json_from())['type'], 'set_info') + + td: TeamDraw = await TeamDraw.objects.prefetch_related('participation__team').aget(pk=td.pk) + self.assertIsNotNone(td.purposed) + self.assertIn(td.purposed, range(1, len(settings.PROBLEMS) + 1)) + + # Assume that this is the problem 1 for teams 2 et 4 and the problem 2 for teams 3 and 5 + td.purposed = 1 + (i % 2) + await td.asave() + + # Render page + resp = await self.async_client.get(reverse('draw:index')) + self.assertEqual(resp.status_code, 200) + + # Accept the problem + await communicator.send_json_to({'type': 'accept'}) + self.assertEqual(await communicator.receive_json_from(), {'type': 'buttons_visibility', 'visible': False}) + self.assertEqual(await communicator.receive_json_from(), + {'type': 'set_problem', 'round': 1, 'team': trigram, 'problem': 1 + (i % 2)}) + self.assertEqual(await communicator.receive_json_from(), {'type': 'box_visibility', 'visible': True}) + self.assertEqual((await communicator.receive_json_from())['type'], 'set_info') + td: TeamDraw = await TeamDraw.objects.prefetch_related('participation__team').aget(pk=td.pk) + self.assertIsNone(td.purposed) + self.assertEqual(td.accepted, 1 + (i % 2)) + + # Render page + resp = await self.async_client.get(reverse('draw:index')) + self.assertEqual(resp.status_code, 200) + + # Go back to the first team of the pool + p: Pool = await Pool.objects.prefetch_related('current_team__participation__team').aget(round=r, letter=1) + td = p.current_team + trigram = td.participation.team.trigram + self.assertEqual(td.choose_index, 0) + self.assertEqual(await communicator.receive_json_from(), {'type': 'set_active', 'round': 1, 'poule': 'A', + 'team': trigram}) + + # Draw and reject 100 times a problem + for _i in range(100): + # Draw a problem + await communicator.send_json_to({'type': 'draw_problem'}) + self.assertEqual(await communicator.receive_json_from(), {'type': 'box_visibility', 'visible': False}) + self.assertEqual(await communicator.receive_json_from(), {'type': 'buttons_visibility', 'visible': True}) + self.assertEqual((await communicator.receive_json_from())['type'], 'set_info') + + td: TeamDraw = await TeamDraw.objects.prefetch_related('participation__team').aget(pk=td.pk) + self.assertIsNotNone(td.purposed) + # Problems 1 and 2 are not available + self.assertIn(td.purposed, range(3, len(settings.PROBLEMS) + 1)) + + # Reject + await communicator.send_json_to({'type': 'reject'}) + self.assertEqual(await communicator.receive_json_from(), {'type': 'buttons_visibility', 'visible': False}) + self.assertEqual((await communicator.receive_json_from())['type'], 'reject_problem') + self.assertEqual(await communicator.receive_json_from(), {'type': 'box_visibility', 'visible': True}) + self.assertEqual((await communicator.receive_json_from())['type'], 'set_info') + td: TeamDraw = await TeamDraw.objects.prefetch_related('participation__team').aget(pk=td.pk) + self.assertIsNone(td.purposed) + self.assertIn(purposed, td.rejected) + + # Ensures that this is still the first team + p: Pool = await Pool.objects.prefetch_related('current_team__participation__team').aget(round=r, letter=1) + self.assertEqual(p.current_team, td) + self.assertEqual(await communicator.receive_json_from(), {'type': 'set_active', 'round': 1, 'poule': 'A', + 'team': trigram}) + + # Render page + resp = await self.async_client.get(reverse('draw:index')) + self.assertEqual(resp.status_code, 200) + + # Ensures that there is a penalty + self.assertGreaterEqual(td.penalty, 1) + + # Draw a last problem + await communicator.send_json_to({'type': 'draw_problem'}) + self.assertEqual(await communicator.receive_json_from(), {'type': 'box_visibility', 'visible': False}) + self.assertEqual(await communicator.receive_json_from(), {'type': 'buttons_visibility', 'visible': True}) + self.assertEqual((await communicator.receive_json_from())['type'], 'set_info') + + td: TeamDraw = await TeamDraw.objects.prefetch_related('participation__team').aget(pk=td.pk) + self.assertIsNotNone(td.purposed) + self.assertIn(td.purposed, range(1, len(settings.PROBLEMS) + 1)) + + # Accept the problem + await communicator.send_json_to({'type': 'accept'}) + self.assertEqual(await communicator.receive_json_from(), {'type': 'buttons_visibility', 'visible': False}) + self.assertEqual(await communicator.receive_json_from(), + {'type': 'set_problem', 'round': 1, 'team': trigram, 'problem': td.purposed}) + td: TeamDraw = await TeamDraw.objects.prefetch_related('participation__team').aget(pk=td.pk) + self.assertIsNone(td.purposed) + + # Reorder the pool since there are 5 teams + self.assertEqual((await communicator.receive_json_from())['type'], 'reorder_poule') + self.assertEqual(await communicator.receive_json_from(), {'type': 'dice_visibility', 'visible': True}) + self.assertEqual((await communicator.receive_json_from())['type'], 'set_info') + self.assertEqual(await communicator.receive_json_from(), + {'type': 'set_active', 'round': 1, 'poule': 'B', 'team': None}) + + # Start pool 2 + r: Round = await Round.objects.prefetch_related('current_pool__current_team__participation__team')\ + .aget(number=1, draw=draw) + p = r.current_pool + self.assertEqual(p.letter, 2) + self.assertEqual(p.size, 4) + self.assertEqual(await p.teamdraw_set.acount(), 4) + self.assertEqual(p.current_team, None) + + # Render page + resp = await self.async_client.get(reverse('draw:index')) + self.assertEqual(resp.status_code, 200) + + i = 0 + async for td in p.teamdraw_set.prefetch_related('participation__team').all(): + # Launch a new dice + await communicator.send_json_to({'type': "dice", 'trigram': td.participation.team.trigram}) + await communicator.receive_json_from() + await td.arefresh_from_db() + td.choice_dice = 101 + i # Avoid duplicates + await td.asave() + i += 1 + + self.assertEqual((await communicator.receive_json_from())['type'], 'set_info') + + for i in range(4): + # Next team + p: Pool = await Pool.objects.prefetch_related('current_team__participation__team').aget(round=r, letter=2) + td = p.current_team + trigram = td.participation.team.trigram + self.assertEqual(td.choose_index, i) + self.assertEqual(await communicator.receive_json_from(), {'type': 'set_active', 'round': 1, 'poule': 'B', + 'team': trigram}) + if i == 0: + self.assertEqual(await communicator.receive_json_from(), {'type': 'dice_visibility', 'visible': False}) + self.assertEqual(await communicator.receive_json_from(), {'type': 'box_visibility', 'visible': True}) + + # Render page + resp = await self.async_client.get(reverse('draw:index')) + self.assertEqual(resp.status_code, 200) + + # Draw a problem + await communicator.send_json_to({'type': 'draw_problem'}) + self.assertEqual(await communicator.receive_json_from(), {'type': 'box_visibility', 'visible': False}) + self.assertEqual(await communicator.receive_json_from(), {'type': 'buttons_visibility', 'visible': True}) + self.assertEqual((await communicator.receive_json_from())['type'], 'set_info') + + td: TeamDraw = await TeamDraw.objects.prefetch_related('participation__team').aget(pk=td.pk) + self.assertIsNotNone(td.purposed) + self.assertIn(td.purposed, range(1, len(settings.PROBLEMS) + 1)) + # Lower problems are already accepted + self.assertGreaterEqual(td.purposed, i + 1) + + # Assume that this is the problem is i for the team i + td.purposed = i + 1 + await td.asave() + + # Render page + resp = await self.async_client.get(reverse('draw:index')) + self.assertEqual(resp.status_code, 200) + + # Accept the problem + await communicator.send_json_to({'type': 'accept'}) + self.assertEqual(await communicator.receive_json_from(), {'type': 'buttons_visibility', 'visible': False}) + self.assertEqual(await communicator.receive_json_from(), + {'type': 'set_problem', 'round': 1, 'team': trigram, 'problem': i + 1}) + if i < 3: + self.assertEqual(await communicator.receive_json_from(), {'type': 'box_visibility', 'visible': True}) + else: + self.assertEqual(await communicator.receive_json_from(), {'type': 'dice_visibility', 'visible': True}) + self.assertEqual((await communicator.receive_json_from())['type'], 'set_info') + td: TeamDraw = await TeamDraw.objects.prefetch_related('participation__team').aget(pk=td.pk) + self.assertIsNone(td.purposed) + self.assertEqual(td.accepted, i + 1) + + # Render page + resp = await self.async_client.get(reverse('draw:index')) + self.assertEqual(resp.status_code, 200) + + # Start pool 3 + r: Round = await Round.objects.prefetch_related('current_pool__current_team__participation__team')\ + .aget(number=1, draw=draw) + p = r.current_pool + self.assertEqual(p.letter, 3) + self.assertEqual(p.size, 3) + self.assertEqual(await p.teamdraw_set.acount(), 3) + self.assertEqual(p.current_team, None) + self.assertEqual(await communicator.receive_json_from(), {'type': 'set_active', 'round': 1, 'poule': 'C', + 'team': None}) + + # Render page + resp = await self.async_client.get(reverse('draw:index')) + self.assertEqual(resp.status_code, 200) + + i = 0 + async for td in p.teamdraw_set.prefetch_related('participation__team').all(): + # Launch a new dice + await communicator.send_json_to({'type': "dice", 'trigram': td.participation.team.trigram}) + await communicator.receive_json_from() + await td.arefresh_from_db() + td.choice_dice = 101 + i # Avoid duplicates + await td.asave() + i += 1 + + self.assertEqual((await communicator.receive_json_from())['type'], 'set_info') + + for i in range(3): + # Next team + p: Pool = await Pool.objects.prefetch_related('current_team__participation__team').aget(round=r, letter=3) + td = p.current_team + trigram = td.participation.team.trigram + self.assertEqual(td.choose_index, i) + self.assertEqual(await communicator.receive_json_from(), {'type': 'set_active', 'round': 1, 'poule': 'C', + 'team': trigram}) + if i == 0: + self.assertEqual(await communicator.receive_json_from(), {'type': 'dice_visibility', 'visible': False}) + self.assertEqual(await communicator.receive_json_from(), {'type': 'box_visibility', 'visible': True}) + + # Render page + resp = await self.async_client.get(reverse('draw:index')) + self.assertEqual(resp.status_code, 200) + + # Draw a problem + await communicator.send_json_to({'type': 'draw_problem'}) + self.assertEqual(await communicator.receive_json_from(), {'type': 'box_visibility', 'visible': False}) + self.assertEqual(await communicator.receive_json_from(), {'type': 'buttons_visibility', 'visible': True}) + self.assertEqual((await communicator.receive_json_from())['type'], 'set_info') + + td: TeamDraw = await TeamDraw.objects.prefetch_related('participation__team').aget(pk=td.pk) + self.assertIsNotNone(td.purposed) + self.assertIn(td.purposed, range(1, len(settings.PROBLEMS) + 1)) + # Lower problems are already accepted + self.assertGreaterEqual(td.purposed, i + 1) + + # Assume that this is the problem is i for the team i + td.purposed = i + 1 + await td.asave() + + # Render page + resp = await self.async_client.get(reverse('draw:index')) + self.assertEqual(resp.status_code, 200) + + # Accept the problem + await communicator.send_json_to({'type': 'accept'}) + self.assertEqual(await communicator.receive_json_from(), {'type': 'buttons_visibility', 'visible': False}) + self.assertEqual(await communicator.receive_json_from(), + {'type': 'set_problem', 'round': 1, 'team': trigram, 'problem': i + 1}) + td: TeamDraw = await TeamDraw.objects.prefetch_related('participation__team').aget(pk=td.pk) + self.assertIsNone(td.purposed) + self.assertEqual(td.accepted, i + 1) + if i == 2: + break + self.assertEqual(await communicator.receive_json_from(), {'type': 'box_visibility', 'visible': True}) + self.assertEqual((await communicator.receive_json_from())['type'], 'set_info') + + # Render page + resp = await self.async_client.get(reverse('draw:index')) + self.assertEqual(resp.status_code, 200) + + # Start round 2 + draw: Draw = await Draw.objects.prefetch_related( + 'current_round__current_pool__current_team__participation__team').aget(tournament=self.tournament) + r = draw.current_round + p = r.current_pool + self.assertEqual(r.number, 2) + self.assertEqual(p.letter, 1) + + for j in range(12): + # Reset dices + self.assertIsNone((await communicator.receive_json_from())['result']) + + # Get pools + resp = await communicator.receive_json_from() + self.assertEqual(resp['type'], 'set_poules') + self.assertEqual(resp['round'], 2) + + self.assertEqual(await communicator.receive_json_from(), {'type': 'dice_visibility', 'visible': True}) + self.assertEqual(await communicator.receive_json_from(), {'type': 'export_visibility', 'visible': True}) + self.assertEqual((await communicator.receive_json_from())['type'], 'set_info') + + # Render page + resp = await self.async_client.get(reverse('draw:index')) + self.assertEqual(resp.status_code, 200) + + for i in range(3): + # Iterate on each pool + r: Round = await Round.objects.prefetch_related('current_pool__current_team__participation__team') \ + .aget(draw=draw, number=2) + p = r.current_pool + self.assertEqual(p.letter, i + 1) + self.assertEqual(p.size, 5 - i) + + self.assertEqual(await communicator.receive_json_from(), + {'type': 'set_active', 'round': 2, 'poule': chr(65 + i), 'team': None}) + + j = 0 + async for td in p.teamdraw_set.prefetch_related('participation__team').all(): + # Launch a new dice + await communicator.send_json_to({'type': "dice", 'trigram': td.participation.team.trigram}) + await communicator.receive_json_from() + await td.arefresh_from_db() + td.choice_dice = 101 + j # Avoid duplicates + await td.asave() + j += 1 + + resp = await communicator.receive_json_from() + self.assertEqual(resp['type'], 'set_info') + + for j in range(5 - i): + # Next team + p: Pool = await Pool.objects.prefetch_related('current_team__participation__team').aget(round=r, + letter=i + 1) + td = p.current_team + trigram = td.participation.team.trigram + self.assertEqual(td.choose_index, j) + self.assertEqual(await communicator.receive_json_from(), + {'type': 'set_active', 'round': 2, 'poule': chr(65 + i), + 'team': trigram}) + if j == 0: + self.assertEqual(await communicator.receive_json_from(), + {'type': 'dice_visibility', 'visible': False}) + self.assertEqual(await communicator.receive_json_from(), + {'type': 'box_visibility', 'visible': True}) + + # Render page + resp = await self.async_client.get(reverse('draw:index')) + self.assertEqual(resp.status_code, 200) + + # Draw a problem + await communicator.send_json_to({'type': 'draw_problem'}) + self.assertEqual(await communicator.receive_json_from(), {'type': 'box_visibility', 'visible': False}) + self.assertEqual(await communicator.receive_json_from(), + {'type': 'buttons_visibility', 'visible': True}) + self.assertEqual((await communicator.receive_json_from())['type'], 'set_info') + + td: TeamDraw = await TeamDraw.objects.prefetch_related('participation__team').aget(pk=td.pk) + self.assertIsNotNone(td.purposed) + self.assertIn(td.purposed, range(1, len(settings.PROBLEMS) + 1)) + # Check that the problem is different from the previous day + old_td = await TeamDraw.objects.aget(round__number=1, round__draw=draw, + participation_id=td.participation_id) + self.assertNotEqual(td.purposed, old_td.accepted) + + # Accept the problem + await communicator.send_json_to({'type': 'accept'}) + self.assertEqual(await communicator.receive_json_from(), + {'type': 'buttons_visibility', 'visible': False}) + self.assertEqual((await communicator.receive_json_from())['type'], 'set_problem') + td: TeamDraw = await TeamDraw.objects.prefetch_related('participation__team').aget(pk=td.pk) + self.assertIsNone(td.purposed) + if j == 4 - i: + break + self.assertEqual(await communicator.receive_json_from(), {'type': 'box_visibility', 'visible': True}) + self.assertEqual((await communicator.receive_json_from())['type'], 'set_info') + + if i == 0: + # Reorder the pool since there are 5 teams + self.assertEqual((await communicator.receive_json_from())['type'], 'reorder_poule') + if i < 2: + self.assertEqual(await communicator.receive_json_from(), {'type': 'dice_visibility', 'visible': True}) + else: + self.assertEqual(await communicator.receive_json_from(), {'type': 'export_visibility', 'visible': True}) + self.assertEqual((await communicator.receive_json_from())['type'], 'set_info') + + self.assertEqual((await communicator.receive_json_from())['type'], 'set_active') + + # Export the draw + await communicator.send_json_to({'type': 'export'}) + self.assertEqual(await communicator.receive_json_from(), {'type': 'export_visibility', 'visible': False}) + + # Cancel all steps and reset all + for i in range(1000): + await communicator.send_json_to({'type': 'cancel'}) + if not await Draw.objects.filter(tournament=self.tournament).aexists(): + break + else: + current_state = (await Draw.objects.filter(tournament=self.tournament).prefetch_related( + 'current_round__current_pool__current_team__participation__team').aget()).get_state() + raise AssertionError("Draw wasn't aborted after 1000 steps, current state: " + current_state) + + # Abort while the tournament is already aborted + await communicator.send_json_to({'type': "abort"}) + + def test_admin_pages(self): + """ + Check that admin pages are rendering successfully. + """ + self.client.force_login(self.superuser) + + draw = Draw.objects.create(tournament=self.tournament) + r1 = Round.objects.create(draw=draw, number=1) + r2 = Round.objects.create(draw=draw, number=2) + p11 = Pool.objects.create(round=r1, letter=1, size=5) + p12 = Pool.objects.create(round=r1, letter=2, size=4) + p13 = Pool.objects.create(round=r1, letter=3, size=3) + p21 = Pool.objects.create(round=r2, letter=1, size=5) + p22 = Pool.objects.create(round=r2, letter=2, size=4) + p23 = Pool.objects.create(round=r2, letter=3, size=3) + tds = [] + for i, team in enumerate(self.teams): + tds.append(TeamDraw.objects.create(participation=team.participation, + round=r1, + pool=p11 if i < 5 else p12 if i < 9 else p13)) + tds.append(TeamDraw.objects.create(participation=team.participation, + round=r2, + pool=p21) if i < 5 else p22 if i < 9 else p23) + + p11.current_team = tds[0] + p11.save() + r1.current_pool = p11 + r1.save() + draw.current_round = r1 + draw.save() + + response = self.client.get(reverse("admin:index") + "draw/draw/") + self.assertEqual(response.status_code, 200) + + response = self.client.get(reverse("admin:index") + + f"draw/draw/{draw.pk}/change/") + self.assertEqual(response.status_code, 200) + response = self.client.get(reverse("admin:index") + + f"r/{ContentType.objects.get_for_model(Draw).id}/" + f"{draw.pk}/") + self.assertRedirects(response, "http://" + Site.objects.get().domain + + str(draw.get_absolute_url()), 302, 200) + + response = self.client.get(reverse("admin:index") + "draw/round/") + self.assertEqual(response.status_code, 200) + + response = self.client.get(reverse("admin:index") + + f"draw/round/{r1.pk}/change/") + self.assertEqual(response.status_code, 200) + response = self.client.get(reverse("admin:index") + + f"draw/round/{r2.pk}/change/") + self.assertEqual(response.status_code, 200) + response = self.client.get(reverse("admin:index") + + f"r/{ContentType.objects.get_for_model(Round).id}/" + f"{r1.pk}/") + self.assertRedirects(response, "http://" + Site.objects.get().domain + + str(r1.get_absolute_url()), 302, 200) + + response = self.client.get(reverse("admin:index") + "draw/pool/") + self.assertEqual(response.status_code, 200) + + response = self.client.get(reverse("admin:index") + + f"draw/pool/{p11.pk}/change/") + self.assertEqual(response.status_code, 200) + response = self.client.get(reverse("admin:index") + + f"r/{ContentType.objects.get_for_model(Pool).id}/" + f"{p11.pk}/") + self.assertRedirects(response, "http://" + Site.objects.get().domain + + str(p11.get_absolute_url()), 302, 200) + + response = self.client.get(reverse("admin:index") + "draw/teamdraw/") + self.assertEqual(response.status_code, 200) + + response = self.client.get(reverse("admin:index") + + f"draw/teamdraw/{tds[0].pk}/change/") + self.assertEqual(response.status_code, 200) + response = self.client.get(reverse("admin:index") + + f"r/{ContentType.objects.get_for_model(TeamDraw).id}/" + f"{tds[0].pk}/") + self.assertRedirects(response, "http://" + Site.objects.get().domain + + str(tds[0].get_absolute_url()), 302, 200)