from __future__ import annotations from unittest.mock import AsyncMock, MagicMock import pytest from yuxi.channels.adapters.wechat.secrets import WeChatSecretsAdapter class TestWeChatSecretsAdapter: def setup_method(self): self.adapter = WeChatSecretsAdapter() def test_build_watchlist_root_config(self): config = { "corp_secret": "secret123", "app_secret": "appsecret", "token": "mytoken", } snap = self.adapter.build_watchlist(config) assert "corp_secret" in snap assert "app_secret" in snap assert "token" in snap assert "bridge_url" not in snap def test_build_watchlist_accounts(self): config = { "accounts": { "acc1": {"corp_secret": "s1", "app_secret": "s2"}, "acc2": {"token": "t1"}, } } snap = self.adapter.build_watchlist(config) assert "accounts.acc1.corp_secret" in snap assert "accounts.acc1.app_secret" in snap assert "accounts.acc2.token" in snap def test_build_watchlist_empty_config(self): snap = self.adapter.build_watchlist({}) assert snap == {} def test_detect_changes_initial_no_changes(self): config = {"corp_secret": "secret1"} changed = self.adapter.detect_changes(config) assert len(changed) == 0 def test_detect_changes_after_update(self): self.adapter.detect_changes({"corp_secret": "old_secret"}) changed = self.adapter.detect_changes({"corp_secret": "new_secret"}) assert "corp_secret" in changed def test_detect_changes_no_change(self): self.adapter.detect_changes({"corp_secret": "stable"}) changed = self.adapter.detect_changes({"corp_secret": "stable"}) assert len(changed) == 0 def test_detect_changes_new_key_added_to_watchlist(self): self.adapter.detect_changes({"corp_secret": "s1"}) changed = self.adapter.detect_changes({"corp_secret": "s1", "app_secret": "new_app"}) assert "app_secret" not in changed def test_should_check_first_time(self): assert self.adapter.should_check() is True def test_should_check_too_soon(self): assert self.adapter.should_check() is True assert self.adapter.should_check() is False @pytest.mark.asyncio async def test_monitor_and_reload_no_change(self): self.adapter.detect_changes({"corp_secret": "stable"}) reload_fn = AsyncMock() await self.adapter.monitor_and_reload({"corp_secret": "stable"}, reload_fn) reload_fn.assert_not_called() @pytest.mark.asyncio async def test_monitor_and_reload_with_change(self): self.adapter._last_check = 0.0 self.adapter.detect_changes({"corp_secret": "old"}) reload_fn = AsyncMock() await self.adapter.monitor_and_reload({"corp_secret": "new"}, reload_fn) reload_fn.assert_called_once() @pytest.mark.asyncio async def test_monitor_and_reload_skips_interval(self): self.adapter._last_check = 999999999999.0 reload_fn = AsyncMock() await self.adapter.monitor_and_reload({"corp_secret": "any"}, reload_fn) reload_fn.assert_not_called() @pytest.mark.asyncio async def test_register_and_notify_subscriber(self): callback = MagicMock() self.adapter.register_subscriber(callback) reload_fn = AsyncMock() self.adapter._last_check = 0.0 self.adapter.detect_changes({"corp_secret": "old"}) await self.adapter.monitor_and_reload({"corp_secret": "new"}, reload_fn) callback.assert_called_once() def test_register_duplicate_subscriber(self): callback = MagicMock() self.adapter.register_subscriber(callback) self.adapter.register_subscriber(callback) assert len(self.adapter._subscribers) == 1 @pytest.mark.asyncio async def test_subscriber_exception_does_not_crash(self): def failing_cb(_keys): raise RuntimeError("callback error") good_cb = MagicMock() self.adapter.register_subscriber(failing_cb) self.adapter.register_subscriber(good_cb) reload_fn = AsyncMock() self.adapter._last_check = 0.0 self.adapter.detect_changes({"corp_secret": "old"}) await self.adapter.monitor_and_reload({"corp_secret": "new"}, reload_fn) good_cb.assert_called_once() def test_snapshot_secrets_root(self): config = {"corp_secret": "s1", "token": "t1"} items = self.adapter.snapshot_secrets(config) assert len(items) == 2 sources = {item["source"] for item in items} assert "root config" in sources def test_snapshot_secrets_accounts(self): config = { "accounts": { "acc1": {"app_secret": "as1"}, } } items = self.adapter.snapshot_secrets(config) assert len(items) == 1 assert items[0]["source"] == "accounts.acc1" def test_snapshot_secrets_empty(self): items = self.adapter.snapshot_secrets({}) assert items == []