mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-10-26 13:16:24 +00:00
`store.py` already treats a state file whose bytes are not UTF-8 as corruption: `_read_legacy_file` catches `UnicodeDecodeError` and the three call paths start fresh or skip the import (#7233, AGENTS.md: "Store JSON decode errors follow corrupt-file recovery"). Four readers in the same package read the same kind of file with an explicit `encoding="utf-8"` but catch only `json.JSONDecodeError`/`OSError`; `UnicodeDecodeError` is a `ValueError`, so it escapes and takes the caller down instead of following that policy: * `ChannelRuntimeConfigStore._load` — `ChannelService.start()` and the channel-connections router construct the store, so one undecodable `runtime-config.json` stops every IM channel from starting, including the non-fallback path where a peer's copy should be rewritten from this process's snapshot. * `_read_legacy_file` — the one-time `runtime-config.json` import aborts instead of keeping the file and reporting it as corrupt. * `WechatChannel._load_state` / `_load_auth_state` — `start()` loads both through `asyncio.to_thread`, so an undecodable cursor or auth file raises out of the channel's own start instead of warning and continuing unauthenticated. Each reader now names `UnicodeDecodeError` next to the existing handlers, so the outcome matches the sibling store: a warning, the in-memory fallback, and the file left in place.
79 lines
3.3 KiB
Python
79 lines
3.3 KiB
Python
"""Undecodable channel state follows the corrupt-state recovery policy, not a crash.
|
|
|
|
``app/channels/store.py`` recovers from a state file whose bytes are not UTF-8
|
|
(``_read_legacy_file`` catches ``UnicodeDecodeError`` and starts fresh), and its
|
|
AGENTS.md states that rule for channel store JSON. The other state readers in the
|
|
same package read the same kind of file with an explicit ``encoding="utf-8"`` but
|
|
only catch ``json.JSONDecodeError`` and ``OSError``; ``UnicodeDecodeError`` is a
|
|
``ValueError``, not an ``OSError``, so it escapes and the channel cannot start.
|
|
|
|
Each test writes undecodable bytes into the file that reader owns and asserts the
|
|
documented outcome: a warning, the in-memory fallback, and the file left in place.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import logging
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from app.channels import runtime_config_store
|
|
from app.channels.message_bus import MessageBus
|
|
from app.channels.wechat import WechatChannel
|
|
|
|
UNDECODABLE = b'{"enabled": "\xff"}'
|
|
|
|
|
|
def test_runtime_config_store_starts_fresh_on_undecodable_state(tmp_path: Path, caplog: pytest.LogCaptureFixture) -> None:
|
|
path = tmp_path / "channels" / "runtime-config.json"
|
|
path.parent.mkdir(parents=True)
|
|
path.write_bytes(UNDECODABLE)
|
|
|
|
with caplog.at_level(logging.WARNING, logger="app.channels.runtime_config_store"):
|
|
store = runtime_config_store.ChannelRuntimeConfigStore(path)
|
|
|
|
assert store.get_provider_config("buzz") is None
|
|
assert "Corrupt channel runtime config store" in caplog.text
|
|
assert path.read_bytes() == UNDECODABLE
|
|
|
|
|
|
def test_runtime_config_store_reports_the_peer_copy_on_undecodable_state(tmp_path: Path, caplog: pytest.LogCaptureFixture) -> None:
|
|
"""The reload path under ``_mutation`` must fall back to this process's copy."""
|
|
path = tmp_path / "channels" / "runtime-config.json"
|
|
store = runtime_config_store.ChannelRuntimeConfigStore(path)
|
|
store.set_provider_config("buzz", {"enabled": True})
|
|
path.write_bytes(UNDECODABLE)
|
|
|
|
with caplog.at_level(logging.WARNING, logger="app.channels.runtime_config_store"):
|
|
store.set_provider_config("slack", {"enabled": True})
|
|
|
|
assert "rewriting it from this process's copy" in caplog.text
|
|
assert store.get_provider_config("buzz") == {"enabled": True}
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_wechat_starts_on_undecodable_state(tmp_path: Path, caplog: pytest.LogCaptureFixture) -> None:
|
|
(tmp_path / "wechat-getupdates.json").write_bytes(UNDECODABLE)
|
|
channel = WechatChannel(MessageBus(), config={"bot_token": "test-token", "state_dir": str(tmp_path)})
|
|
|
|
with caplog.at_level(logging.WARNING, logger="app.channels.wechat"):
|
|
channel._load_state()
|
|
|
|
assert channel._get_updates_buf == ""
|
|
assert "failed to read cursor state" in caplog.text
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_wechat_starts_on_undecodable_auth_state(tmp_path: Path, caplog: pytest.LogCaptureFixture) -> None:
|
|
(tmp_path / "wechat-auth.json").write_bytes(UNDECODABLE)
|
|
channel = WechatChannel(MessageBus(), config={"bot_token": "", "state_dir": str(tmp_path), "qrcode_login_enabled": True})
|
|
|
|
with caplog.at_level(logging.WARNING, logger="app.channels.wechat"):
|
|
channel._load_auth_state()
|
|
|
|
assert channel._bot_token == ""
|
|
assert "failed to read auth state" in caplog.text
|
|
assert json.dumps({}) == "{}"
|