from __future__ import annotations from unittest.mock import AsyncMock, MagicMock, patch import pytest from yuxi.channels.manager import ChannelManager from yuxi.channels.registry import ChannelRegistry from yuxi.channels.services.runtime_state import RuntimeState class TestChannelManagerPhases: def test_initial_phase(self): manager = ChannelManager() assert manager.phase == "not_started" @pytest.mark.asyncio async def test_load_config_sets_phase(self): with ( patch("yuxi.channels.manager._load_builtin_adapters"), patch("yuxi.channels.manager._BUILTIN_ADAPTERS", {}), patch("yuxi.config") as mock_config, ): mock_config.channels = {} manager = ChannelManager() await manager.load_config() assert manager.phase == "config_loaded" @pytest.mark.asyncio async def test_load_config_idempotent(self): with ( patch("yuxi.channels.manager._load_builtin_adapters"), patch("yuxi.channels.manager._BUILTIN_ADAPTERS", {}), patch("yuxi.config") as mock_config, ): mock_config.channels = {} manager = ChannelManager() await manager.load_config() mock_config.channels = {"wx": {"enabled": True}} await manager.load_config() assert manager._channels_config == {} @pytest.mark.asyncio async def test_initialize_sets_fully_running(self): with ( patch("yuxi.channels.manager._load_builtin_adapters"), patch("yuxi.channels.manager._BUILTIN_ADAPTERS", {}), patch("yuxi.config") as mock_config, ): mock_config.channels = {} mock_pg = MagicMock() mock_db = AsyncMock() mock_pg.__aenter__ = AsyncMock(return_value=mock_db) mock_pg.__aexit__ = AsyncMock(return_value=None) with ( patch( "yuxi.channels.manager.pg_manager.get_async_session_context", return_value=mock_pg, ), patch.object(ChannelManager, "_ensure_virtual_department", AsyncMock()), patch.object(ChannelManager, "_ensure_default_agent_config", AsyncMock()), ): manager = ChannelManager() await manager.initialize() assert manager.phase == "fully_running" assert manager._initialized is True @pytest.mark.asyncio async def test_start_subscriptions_only_after_channels_started(self): manager = ChannelManager() assert manager.phase == "not_started" await manager.start_subscriptions() assert manager.phase == "not_started" @pytest.mark.asyncio async def test_prepare_bootstrap_only_after_config_loaded(self): manager = ChannelManager() await manager.prepare_bootstrap() assert manager.phase == "not_started" @pytest.mark.asyncio async def test_start_channels_allows_from_config_loaded_or_bootstrapped(self): with ( patch("yuxi.channels.manager._load_builtin_adapters"), patch("yuxi.channels.manager._BUILTIN_ADAPTERS", {}), patch("yuxi.config") as mock_config, ): mock_config.channels = {} registry = ChannelRegistry() manager = ChannelManager(registry=registry) await manager.load_config() assert manager.phase == "config_loaded" await manager.start_channels() assert manager.phase == "channels_started" @pytest.mark.asyncio async def test_startup_idempotent(self): with ( patch("yuxi.channels.manager._load_builtin_adapters"), patch("yuxi.channels.manager._BUILTIN_ADAPTERS", {}), patch("yuxi.config") as mock_config, ): mock_config.channels = {} manager = ChannelManager() manager._initialized = True await manager.startup() assert manager.phase == "not_started" @pytest.mark.asyncio async def test_startup_creates_context(self): with ( patch("yuxi.channels.manager._load_builtin_adapters"), patch("yuxi.channels.manager._BUILTIN_ADAPTERS", {}), patch("yuxi.config") as mock_config, ): mock_config.channels = {} mock_pg = MagicMock() mock_db = AsyncMock() mock_pg.__aenter__ = AsyncMock(return_value=mock_db) mock_pg.__aexit__ = AsyncMock(return_value=None) with ( patch( "yuxi.channels.manager.pg_manager.get_async_session_context", return_value=mock_pg, ), patch.object(ChannelManager, "_ensure_virtual_department", AsyncMock()), patch.object(ChannelManager, "_ensure_default_agent_config", AsyncMock()), ): manager = ChannelManager() await manager.startup() assert manager.context is not None assert manager.broadcaster is not None assert manager.watcher is not None assert manager.doctor is not None assert manager.phase == "fully_running" def test_new_properties_default(self): manager = ChannelManager() assert manager.context is None assert manager.broadcaster is None assert manager.watcher is None assert manager.doctor is None assert isinstance(manager.runtime_state, RuntimeState) assert manager.runtime_state.phase == "not_started" @pytest.mark.asyncio async def test_diagnose_channels_no_doctor(self): manager = ChannelManager() issues = await manager.diagnose_channels() assert issues == [] @pytest.mark.asyncio async def test_auto_fix_no_doctor(self): manager = ChannelManager() result = await manager.auto_fix_channel(None) assert result is False @pytest.mark.asyncio async def test_reload_config_no_watcher(self): manager = ChannelManager() result = await manager.reload_config_now() assert result == {}