2026-05-29 23:14:48 -04:00
|
|
|
"""Channels ITs for MySeaSpectateConsumer — the async-witness WS that pushes
|
|
|
|
|
the owner's draw to watching invitees (user-spec 2026-05-29).
|
|
|
|
|
|
|
|
|
|
Same shape as the voice consumer tests: scope injected directly, in-memory
|
|
|
|
|
channel layer, `@tag("channels")` (excluded from the default run).
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from channels.db import database_sync_to_async
|
|
|
|
|
from channels.testing.websocket import WebsocketCommunicator
|
|
|
|
|
from django.contrib.auth.models import AnonymousUser
|
|
|
|
|
from django.test import TransactionTestCase, override_settings, tag
|
|
|
|
|
from django.utils import timezone
|
|
|
|
|
|
|
|
|
|
from apps.gameboard.consumers import MySeaSpectateConsumer
|
|
|
|
|
from apps.gameboard.models import SeaInvite
|
|
|
|
|
from apps.lyric.models import User
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TEST_CHANNEL_LAYERS = {
|
|
|
|
|
"default": {"BACKEND": "channels.layers.InMemoryChannelLayer"},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@tag("channels")
|
|
|
|
|
@override_settings(CHANNEL_LAYERS=TEST_CHANNEL_LAYERS)
|
|
|
|
|
class MySeaSpectateConsumerTest(TransactionTestCase):
|
|
|
|
|
def setUp(self):
|
|
|
|
|
self.owner = User.objects.create(email="owner@test.io", username="discoman")
|
|
|
|
|
self.bud = User.objects.create(email="bud@test.io", username="budster")
|
|
|
|
|
|
|
|
|
|
def _comm(self, user):
|
|
|
|
|
comm = WebsocketCommunicator(
|
|
|
|
|
MySeaSpectateConsumer.as_asgi(),
|
|
|
|
|
f"/ws/my-sea-spectate/{self.owner.id}/",
|
|
|
|
|
)
|
|
|
|
|
comm.scope["user"] = user
|
|
|
|
|
comm.scope["url_route"] = {"kwargs": {"owner_id": self.owner.id}}
|
|
|
|
|
return comm
|
|
|
|
|
|
|
|
|
|
def _invite(self, present):
|
|
|
|
|
return SeaInvite.objects.create(
|
|
|
|
|
owner=self.owner, invitee=self.bud, invitee_email=self.bud.email,
|
|
|
|
|
status=SeaInvite.ACCEPTED, accepted_at=timezone.now(),
|
|
|
|
|
token_deposited_at=timezone.now() if present else None,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
async def test_owner_can_watch(self):
|
|
|
|
|
comm = self._comm(self.owner)
|
|
|
|
|
connected, _ = await comm.connect()
|
|
|
|
|
self.assertTrue(connected)
|
|
|
|
|
await comm.disconnect()
|
|
|
|
|
|
|
|
|
|
async def test_unauthenticated_is_rejected(self):
|
|
|
|
|
comm = self._comm(AnonymousUser())
|
|
|
|
|
connected, _ = await comm.connect()
|
|
|
|
|
self.assertFalse(connected)
|
|
|
|
|
|
|
|
|
|
async def test_stranger_is_rejected(self):
|
|
|
|
|
comm = self._comm(self.bud) # no invite
|
|
|
|
|
connected, _ = await comm.connect()
|
|
|
|
|
self.assertFalse(connected)
|
|
|
|
|
|
|
|
|
|
async def test_accepted_but_not_present_is_rejected(self):
|
|
|
|
|
await database_sync_to_async(self._invite)(present=False)
|
|
|
|
|
comm = self._comm(self.bud)
|
|
|
|
|
connected, _ = await comm.connect()
|
|
|
|
|
self.assertFalse(connected)
|
|
|
|
|
|
|
|
|
|
async def test_present_invitee_receives_the_owners_hand(self):
|
|
|
|
|
await database_sync_to_async(self._invite)(present=True)
|
|
|
|
|
comm = self._comm(self.bud)
|
|
|
|
|
connected, _ = await comm.connect()
|
|
|
|
|
self.assertTrue(connected)
|
|
|
|
|
from channels.layers import get_channel_layer
|
|
|
|
|
hand = [{"position": "lay", "card_id": 1,
|
|
|
|
|
"reversed": False, "polarity": "gravity"}]
|
|
|
|
|
await get_channel_layer().group_send(
|
|
|
|
|
f"mysea_{self.owner.id}", {"type": "sea_draw", "hand": hand})
|
|
|
|
|
msg = await comm.receive_json_from()
|
|
|
|
|
self.assertEqual(msg, {"type": "sea_draw", "hand": hand})
|
|
|
|
|
await comm.disconnect()
|
2026-05-29 23:42:22 -04:00
|
|
|
|
|
|
|
|
async def test_present_invitee_receives_seat_updates(self):
|
|
|
|
|
await database_sync_to_async(self._invite)(present=True)
|
|
|
|
|
comm = self._comm(self.bud)
|
|
|
|
|
connected, _ = await comm.connect()
|
|
|
|
|
self.assertTrue(connected)
|
|
|
|
|
from channels.layers import get_channel_layer
|
|
|
|
|
seats = [{"n": 1, "label": "1C", "present": True, "token": "owner-x"}]
|
|
|
|
|
await get_channel_layer().group_send(
|
|
|
|
|
f"mysea_{self.owner.id}", {"type": "sea_seats", "seats": seats})
|
|
|
|
|
msg = await comm.receive_json_from()
|
|
|
|
|
self.assertEqual(msg, {"type": "sea_seats", "seats": seats})
|
|
|
|
|
await comm.disconnect()
|