from __future__ import annotations from unittest.mock import AsyncMock, MagicMock import pytest from yuxi.channels.infra.config_watcher import ConfigWatcher class TestConfigWatcherInit: def test_default_values(self): manager = MagicMock() watcher = ConfigWatcher(manager) assert watcher._last_config_hash is None assert watcher._watched_prefixes == ["channels"] assert watcher._noop_prefixes == [] assert watcher._running is False def test_set_watched_prefixes(self): manager = MagicMock() watcher = ConfigWatcher(manager) watcher.set_watched_prefixes(["channels.wechat", "channels.telegram"]) assert watcher._watched_prefixes == ["channels.wechat", "channels.telegram"] def test_set_noop_prefixes(self): manager = MagicMock() watcher = ConfigWatcher(manager) watcher.set_noop_prefixes(["channels.wechat.logLevel"]) assert watcher._noop_prefixes == ["channels.wechat.logLevel"] class TestConfigWatcherCompute: def test_compute_config_hash_consistent(self): manager = MagicMock() manager._channels_config = {"wx": {"enabled": True}} watcher = ConfigWatcher(manager) h1 = watcher._compute_config_hash() h2 = watcher._compute_config_hash() assert h1 == h2 def test_compute_config_hash_changes_with_config(self): manager = MagicMock() manager._channels_config = {"wx": {"enabled": True}} watcher = ConfigWatcher(manager) h1 = watcher._compute_config_hash() manager._channels_config = {"wx": {"enabled": False}} h2 = watcher._compute_config_hash() assert h1 != h2 class TestConfigWatcherDiff: def test_compute_diff_new_channel(self): manager = MagicMock() manager._channels_config = {"wx": {"enabled": True}} manager._adapters = {} watcher = ConfigWatcher(manager) diff = watcher._compute_config_diff() assert diff == {"wx": {"action": "start", "config": {"enabled": True}}} def test_compute_diff_stopped_channel(self): manager = MagicMock() manager._channels_config = {} manager._adapters = {"wx": MagicMock()} watcher = ConfigWatcher(manager) diff = watcher._compute_config_diff() assert diff == {"wx": {"action": "stop"}} def test_compute_diff_changed_channel(self): manager = MagicMock() manager._channels_config = {"wx": {"enabled": True}} manager._adapters = {"wx": MagicMock()} watcher = ConfigWatcher(manager) diff = watcher._compute_config_diff() assert diff == {"wx": {"action": "restart"}} class TestConfigWatcherLifecycle: @pytest.mark.asyncio async def test_watch_starts_and_stops(self): manager = MagicMock() manager._channels_config = {} manager._adapters = {} watcher = ConfigWatcher(manager) await watcher.watch(interval=60.0) assert watcher._running is True assert watcher._task is not None await watcher.stop() assert watcher._running is False @pytest.mark.asyncio async def test_watch_idempotent(self): manager = MagicMock() manager._channels_config = {} manager._adapters = {} watcher = ConfigWatcher(manager) await watcher.watch(interval=60.0) task1 = watcher._task await watcher.watch(interval=60.0) assert watcher._task is task1 await watcher.stop() @pytest.mark.asyncio async def test_reload_now_no_keys(self): manager = MagicMock() manager._channels_config = {"wx": {"enabled": True}} manager._adapters = {} manager.start_channel = AsyncMock() watcher = ConfigWatcher(manager) result = await watcher.reload_now() assert "wx" in result