From 655a4fb2046cd10c8c51e633b462dff8984ad82a Mon Sep 17 00:00:00 2001 From: InfinityPacer Date: Mon, 31 Aug 2026 07:33:40 +0800 Subject: [PATCH] fix(notification): keep config save independent from cache sync --- app/api/endpoints/notification.py | 24 ++++++--- tests/test_notification_config_identity.py | 59 +++++++++++++++++++++- 2 files changed, 74 insertions(+), 9 deletions(-) diff --git a/app/api/endpoints/notification.py b/app/api/endpoints/notification.py index 96941cbdb..32cf1345d 100644 --- a/app/api/endpoints/notification.py +++ b/app/api/endpoints/notification.py @@ -6,6 +6,7 @@ from app.api.dependencies.auth import get_current_active_superuser from app.api.response import ResponseAPIRouter from app.application.configuration import get_configured_system_config from app.chain.notification import NotificationChain +from app.modules.wechatclawbot.wechatclawbot import WechatClawBot from app.schemas.common import ManageRequest as _SchemaManageRequest from app.schemas.response import Response as _SchemaResponse from app.schemas.system import NotificationConf @@ -54,17 +55,24 @@ async def save_config( legacy["id"] = f"legacy-{legacy.get('type') or 'notification'}-{legacy.get('name') or 'unnamed'}" previous_models.append(NotificationConf(**legacy)) previous_data = _normalize_configs(previous_models) - result = NotificationChain().manage_channel( - channel="WechatClawBot", - action="reconcile_config", - previous=previous_data, - current=current, - ) - if not result.get("success"): - return _SchemaResponse(success=False, message=result.get("message")) saved = await get_configured_system_config().async_set(SystemConfigKey.Notifications, current) if saved is False: return _SchemaResponse(success=False, message="通知配置保存失败") + try: + # 配置写入成功后再同步 ClawBot 缓存,避免持久化失败留下半完成的登录状态。 + result = WechatClawBot.reconcile_cached_states(previous_data, current) + except Exception as error: + return _SchemaResponse( + success=False, + message=f"通知配置已保存,但缓存同步失败,请重试:{error}", + data={"value": current}, + ) + if not result.get("success"): + return _SchemaResponse( + success=False, + message=f"通知配置已保存,但缓存同步失败,请重试:{result.get('message')}", + data={"value": current}, + ) return _SchemaResponse(success=True, message="通知配置保存成功", data={"value": current}) except ValueError as error: return _SchemaResponse(success=False, message=str(error)) diff --git a/tests/test_notification_config_identity.py b/tests/test_notification_config_identity.py index 11cbc9e50..11f3649e5 100644 --- a/tests/test_notification_config_identity.py +++ b/tests/test_notification_config_identity.py @@ -2,7 +2,7 @@ import pytest -from app.api.endpoints.notification import _normalize_configs +from app.api.endpoints.notification import _normalize_configs, save_config from app.modules.wechatclawbot.wechatclawbot import WechatClawBot from app.schemas.system import NotificationConf @@ -61,3 +61,60 @@ def test_reconcile_cached_states_migrates_renamed_identity_and_cleans_removed(mo @pytest.mark.parametrize("identity", ["stable-id", "display-name"]) def test_cache_key_is_stable_for_same_identity(identity): assert WechatClawBot._build_cache_key(identity) == WechatClawBot._build_cache_key(identity) + + +@pytest.mark.asyncio +async def test_save_config_does_not_touch_cache_when_persistence_fails(monkeypatch): + calls = {"reconcile": 0} + + class FakeConfig: + def get(self, _key): + return [] + + async def async_set(self, _key, _value): + return False + + monkeypatch.setattr( + "app.api.endpoints.notification.get_configured_system_config", + lambda: FakeConfig(), + ) + + def fail_if_reconciled(_previous, _current): + calls["reconcile"] += 1 + return {"success": True} + + monkeypatch.setattr(WechatClawBot, "reconcile_cached_states", fail_if_reconciled) + response = await save_config([NotificationConf(name="Alpha", type="telegram")], None) + + assert response.success is False + assert response.message == "通知配置保存失败" + assert calls["reconcile"] == 0 + + +@pytest.mark.asyncio +async def test_save_config_saves_channels_without_loaded_clawbot_module(monkeypatch): + calls = [] + + class FakeConfig: + def get(self, _key): + return [] + + async def async_set(self, _key, value): + calls.append(value) + return True + + monkeypatch.setattr( + "app.api.endpoints.notification.get_configured_system_config", + lambda: FakeConfig(), + ) + monkeypatch.setattr( + WechatClawBot, + "reconcile_cached_states", + lambda _previous, _current: {"success": True}, + ) + + response = await save_config([NotificationConf(name="TG", type="telegram")], None) + + assert response.success is True + assert response.data["value"][0]["name"] == "TG" + assert len(calls) == 1