diff --git a/app/application/system.py b/app/application/system.py index f990eed74..007707005 100644 --- a/app/application/system.py +++ b/app/application/system.py @@ -317,7 +317,8 @@ class SystemService: success = await self._system_config.async_set(key, value) if success: await self._events.publish(key, event_value) - return SystemOperationResult(True) + # None 表示值未变化,仍是成功完成;只有明确 False 才代表持久化失败。 + return SystemOperationResult(success is not False) except PluginMutationRejectedError as error: return SystemOperationResult(False, str(error)) diff --git a/tests/test_plugin_system_setting_admission.py b/tests/test_plugin_system_setting_admission.py index 7d291e96b..ddb5fbb6a 100644 --- a/tests/test_plugin_system_setting_admission.py +++ b/tests/test_plugin_system_setting_admission.py @@ -216,6 +216,28 @@ async def test_non_plugin_system_config_does_not_resolve_plugin_runtime( config.async_set.assert_awaited_once_with(SystemConfigKey.Directories.value, None) +@pytest.mark.asyncio +async def test_system_http_reports_persistent_write_failure(monkeypatch) -> None: + """通用系统配置持久化失败时 HTTP 响应和事件均不得伪造成功。""" + config = MagicMock() + config.async_set = AsyncMock(return_value=False) + send_event = AsyncMock() + monkeypatch.setattr( + "app.startup.composition.system.eventmanager.async_send_event", send_event + ) + + response = await system_endpoint.set_setting( + SystemConfigKey.Directories.value, + [], + None, + runtime=_runtime(config), + ) + + assert response.success is False + config.async_set.assert_awaited_once_with(SystemConfigKey.Directories.value, None) + send_event.assert_not_awaited() + + @pytest.mark.asyncio async def test_rule_group_setting_reconciles_stale_references_when_unchanged( monkeypatch, diff --git a/tests/test_system_application.py b/tests/test_system_application.py index 55ec16acb..b3112ebd1 100644 --- a/tests/test_system_application.py +++ b/tests/test_system_application.py @@ -221,6 +221,34 @@ async def test_update_setting_routes_runtime_and_persistent_values() -> None: ) +@pytest.mark.anyio +@pytest.mark.parametrize( + ("persisted_result", "expected_success", "publishes_event"), + [(True, True, True), (None, True, False), (False, False, False)], +) +async def test_update_setting_preserves_persistent_write_result( + persisted_result: bool | None, + expected_success: bool, + publishes_event: bool, +) -> None: + """通用系统配置写入只把明确 False 映射为失败并抑制失败事件。""" + service, dependencies = _build_service() + dependencies.settings.contains.return_value = False + dependencies.system_config.async_set.return_value = persisted_result + + result = await service.update_setting( + SystemConfigKey.IndexerSites.value, [1, None, 2] + ) + + assert result.success is expected_success + if publishes_event: + dependencies.events.publish.assert_awaited_once_with( + SystemConfigKey.IndexerSites.value, [1, 2] + ) + else: + dependencies.events.publish.assert_not_awaited() + + @pytest.mark.anyio async def test_update_setting_applies_rule_groups_and_reports_mutation_rejection() -> None: """规则组使用原子 mutation,插件写入拒绝应转换为稳定结果。"""