"""Discord adapter race polish: concurrent join_voice_channel must not double-invoke channel.connect() on the same guild.""" import asyncio from unittest.mock import MagicMock, patch import pytest from gateway.config import Platform, PlatformConfig def _make_adapter(): from gateway.platforms.discord import DiscordAdapter adapter = object.__new__(DiscordAdapter) adapter._platform = Platform.DISCORD adapter.config = PlatformConfig(enabled=True, token="t") adapter._ready_event = asyncio.Event() adapter._allowed_user_ids = set() adapter._allowed_role_ids = set() adapter._voice_clients = {} adapter._voice_locks = {} adapter._voice_receivers = {} adapter._voice_listen_tasks = {} adapter._voice_timeout_tasks = {} adapter._voice_text_channels = {} adapter._voice_sources = {} adapter._client = MagicMock() return adapter @pytest.mark.asyncio async def test_concurrent_joins_do_not_double_connect(): """Two concurrent join_voice_channel calls on the same guild must serialize through the per-guild lock — only ONE channel.connect() actually fires; the second sees the _voice_clients entry the first just installed.""" adapter = _make_adapter() connect_count = [0] release = asyncio.Event() class FakeVC: def __init__(self, channel): self.channel = channel def is_connected(self): return True async def move_to(self, _channel): return None async def slow_connect(self): connect_count[0] += 1 await release.wait() return FakeVC(self) channel = MagicMock() channel.id = 111 channel.guild.id = 42 channel.connect = lambda: slow_connect(channel) from gateway.platforms import discord as discord_mod with patch.object(discord_mod, "VoiceReceiver", MagicMock(return_value=MagicMock(start=lambda: None))): with patch.object(discord_mod.asyncio, "ensure_future", lambda _c: asyncio.create_task(asyncio.sleep(0))): t1 = asyncio.create_task(adapter.join_voice_channel(channel)) t2 = asyncio.create_task(adapter.join_voice_channel(channel)) await asyncio.sleep(0.05) release.set() r1, r2 = await asyncio.gather(t1, t2) assert connect_count[0] == 1, ( f"expected 1 channel.connect() call, got {connect_count[0]} — " "per-guild lock is not serializing join_voice_channel" ) assert r1 is True and r2 is True assert 42 in adapter._voice_clients