143 lines
5.3 KiB
Python
143 lines
5.3 KiB
Python
import unittest
|
|
from unittest.mock import AsyncMock, Mock, patch
|
|
|
|
from channels.exceptions import InvalidChannelLayerError
|
|
from django.contrib.auth import get_user_model
|
|
from django.test import SimpleTestCase, TestCase
|
|
|
|
try:
|
|
from channels.testing import WebsocketCommunicator
|
|
except Exception: # pragma: no cover - optional test dependency
|
|
WebsocketCommunicator = None
|
|
|
|
from fupogfakta.models import GameSession, Player
|
|
from partyhub.asgi import application
|
|
from realtime.broadcast import broadcast_phase_event, sync_broadcast_phase_event
|
|
from realtime.consumers import GameConsumer
|
|
|
|
User = get_user_model()
|
|
|
|
|
|
class BroadcastPhaseEventTests(SimpleTestCase):
|
|
@patch("realtime.broadcast.get_channel_layer", return_value=None)
|
|
async def test_broadcast_phase_event_noops_without_channel_layer(self, _mock_get_channel_layer):
|
|
await broadcast_phase_event("ABCD", "phase.scoreboard", {"phase": "scoreboard"})
|
|
|
|
@patch("realtime.broadcast.async_to_sync")
|
|
def test_sync_broadcast_phase_event_noops_when_channel_layer_is_unavailable(self, mock_async_to_sync):
|
|
mock_async_to_sync.return_value.side_effect = InvalidChannelLayerError("missing channel layer")
|
|
|
|
sync_broadcast_phase_event("ABCD", "phase.scoreboard", {"phase": "scoreboard"})
|
|
|
|
@patch("realtime.broadcast.async_to_sync")
|
|
def test_sync_broadcast_phase_event_still_broadcasts_when_channel_layer_exists(self, mock_async_to_sync):
|
|
sender = Mock()
|
|
mock_async_to_sync.return_value = sender
|
|
|
|
sync_broadcast_phase_event("ABCD", "phase.scoreboard", {"phase": "scoreboard"})
|
|
|
|
sender.assert_called_once_with("ABCD", "phase.scoreboard", {"phase": "scoreboard"})
|
|
|
|
|
|
class GameConsumerPhaseEventTests(SimpleTestCase):
|
|
async def test_phase_event_restores_external_type_field(self):
|
|
consumer = GameConsumer()
|
|
consumer.send_json = AsyncMock()
|
|
|
|
await consumer.phase_event(
|
|
{
|
|
"event_type": "phase.test_event",
|
|
"payload": {"hello": "world"},
|
|
}
|
|
)
|
|
|
|
consumer.send_json.assert_awaited_once_with(
|
|
{
|
|
"type": "phase.test_event",
|
|
"hello": "world",
|
|
}
|
|
)
|
|
|
|
async def test_disconnect_discards_group_membership(self):
|
|
consumer = GameConsumer()
|
|
consumer.group_name = "game_AABBCC"
|
|
consumer.channel_name = "test-channel"
|
|
consumer.channel_layer = Mock(group_discard=AsyncMock())
|
|
|
|
await consumer.disconnect(1000)
|
|
|
|
consumer.channel_layer.group_discard.assert_awaited_once_with("game_AABBCC", "test-channel")
|
|
|
|
|
|
@unittest.skipIf(WebsocketCommunicator is None, "channels.testing dependencies unavailable")
|
|
class GameConsumerConnectTest(TestCase):
|
|
def setUp(self):
|
|
self.user = User.objects.create_user(username="host", password="pw")
|
|
self.session = GameSession.objects.create(host=self.user, code="AABBCC")
|
|
self.player = Player.objects.create(session=self.session, nickname="Tester")
|
|
|
|
async def test_player_connect_and_ping(self):
|
|
token = self.player.session_token
|
|
communicator = WebsocketCommunicator(
|
|
application,
|
|
f"/ws/game/AABBCC/?session_token={token}",
|
|
)
|
|
connected, _ = await communicator.connect()
|
|
self.assertTrue(connected)
|
|
|
|
await communicator.send_json_to({"type": "ping"})
|
|
response = await communicator.receive_json_from()
|
|
self.assertEqual(response["type"], "pong")
|
|
|
|
await communicator.disconnect()
|
|
|
|
async def test_connect_without_token_rejected(self):
|
|
communicator = WebsocketCommunicator(application, "/ws/game/AABBCC/")
|
|
connected, code = await communicator.connect()
|
|
self.assertFalse(connected)
|
|
self.assertEqual(code, 4001)
|
|
|
|
async def test_connect_invalid_token_rejected(self):
|
|
communicator = WebsocketCommunicator(
|
|
application,
|
|
"/ws/game/AABBCC/?session_token=invalid-token",
|
|
)
|
|
connected, code = await communicator.connect()
|
|
self.assertFalse(connected)
|
|
self.assertEqual(code, 4003)
|
|
|
|
async def test_host_connect_without_token(self):
|
|
communicator = WebsocketCommunicator(
|
|
application,
|
|
"/ws/game/AABBCC/?role=host",
|
|
)
|
|
connected, _ = await communicator.connect()
|
|
self.assertTrue(connected)
|
|
await communicator.disconnect()
|
|
|
|
async def test_host_connect_unknown_session_rejected(self):
|
|
communicator = WebsocketCommunicator(
|
|
application,
|
|
"/ws/game/ZZZZZZ/?role=host",
|
|
)
|
|
connected, code = await communicator.connect()
|
|
self.assertFalse(connected)
|
|
self.assertEqual(code, 4004)
|
|
|
|
async def test_broadcast_reaches_connected_client(self):
|
|
token = self.player.session_token
|
|
communicator = WebsocketCommunicator(
|
|
application,
|
|
f"/ws/game/AABBCC/?session_token={token}",
|
|
)
|
|
connected, _ = await communicator.connect()
|
|
self.assertTrue(connected)
|
|
|
|
await broadcast_phase_event("AABBCC", "phase.test_event", {"hello": "world"})
|
|
|
|
message = await communicator.receive_json_from(timeout=2)
|
|
self.assertEqual(message["type"], "phase.test_event")
|
|
self.assertEqual(message["hello"], "world")
|
|
|
|
await communicator.disconnect()
|