"""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()