140 lines
5.0 KiB
Python
140 lines
5.0 KiB
Python
|
|
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 == []
|