Files
nanobot/tests/channels/test_channel_manager_hot_reload.py
T
Xubin RenandGitHub fe0717b385 feat(webui): add guided setup flows
* feat(channels): add guided setup flows

* test(channels): preserve setup config values

* fix(channels): reflect saved setup state

* refactor(channels): simplify setup state metadata

* fix(channels): harden setup lifecycle

* refactor(channels): centralize setup contracts

* fix(channels): route setup actions through webui shim

* fix(channels): adapt settings for compact screens

* fix(models): preserve default preset display

* feat(models): add curated Codex catalog

* fix(webui): stop attached gateway on interrupt

* fix(webui): simplify apps catalog

* docs(webui): clarify apps and runtime features

* feat(settings): add guided capability setup

* fix(webui): harden setup and managed services

* test: keep managed runtime checks portable

* test: scope POSIX runtime coverage

* fix(webui): simplify file settings

* feat(files): bundle document reading

* fix(webui): harden setup request boundaries

* fix(webui): prevent channel setup status squeeze

* fix(settings): group provider compatibility aliases

* refactor(settings): remove redundant setup surfaces

* fix(webui): harden guided setup lifecycle

* fix(webui): preserve channel setup compatibility
2026-07-13 13:11:46 +08:00

115 lines
3.6 KiB
Python

from __future__ import annotations
import asyncio
import pytest
from nanobot.bus.queue import MessageBus
from nanobot.channels.base import BaseChannel
from nanobot.channels.manager import ChannelManager
from nanobot.config.schema import Config
class _HotChannel(BaseChannel):
name = "hot"
display_name = "Hot"
def __init__(self, config, bus):
super().__init__(config, bus)
self.started = asyncio.Event()
self.stopped = asyncio.Event()
async def start(self):
self._running = True
self.started.set()
await self.stopped.wait()
async def stop(self):
self._running = False
self.stopped.set()
async def send(self, msg): # pragma: no cover - not used by this test
raise AssertionError("send should not be called")
@pytest.mark.asyncio
async def test_apply_channel_feature_action_starts_and_stops_channel(monkeypatch):
disabled = Config.model_validate({
"channels": {
"websocket": {"enabled": False},
"hot": {"enabled": False},
}
})
enabled = Config.model_validate({
"channels": {
"websocket": {"enabled": False},
"hot": {"enabled": True},
}
})
import nanobot.channels.registry as registry
def discover_enabled(enabled_names, **_kwargs):
return {"hot": _HotChannel} if "hot" in enabled_names else {}
configs = iter([enabled, disabled])
monkeypatch.setattr(registry, "discover_channel_names", lambda: ["hot"])
monkeypatch.setattr(registry, "discover_plugins", lambda enabled_names=None: {})
monkeypatch.setattr(registry, "discover_enabled", discover_enabled)
monkeypatch.setattr("nanobot.config.loader.load_config", lambda: next(configs))
manager = ChannelManager(disabled, MessageBus())
manager._started = True
enabled_result = await manager.apply_channel_feature_action("enable", "hot")
assert enabled_result["handled"] is True
assert enabled_result["requires_restart"] is False
channel = manager.channels["hot"]
await asyncio.wait_for(channel.started.wait(), timeout=1)
assert channel.is_running is True
disabled_result = await manager.apply_channel_feature_action("disable", "hot")
assert disabled_result["handled"] is True
assert disabled_result["requires_restart"] is False
assert "hot" not in manager.channels
assert channel.is_running is False
@pytest.mark.asyncio
async def test_apply_channel_feature_action_keeps_running_channel_when_rebuild_fails(monkeypatch):
enabled = Config.model_validate({
"channels": {
"websocket": {"enabled": False},
"hot": {"enabled": True},
}
})
import nanobot.channels.registry as registry
monkeypatch.setattr(registry, "discover_channel_names", lambda: ["hot"])
monkeypatch.setattr(registry, "discover_plugins", lambda enabled_names=None: {})
monkeypatch.setattr(
registry,
"discover_enabled",
lambda enabled_names, **_kwargs: {"hot": _HotChannel},
)
monkeypatch.setattr("nanobot.config.loader.load_config", lambda: enabled)
manager = ChannelManager(enabled, MessageBus())
old_channel = manager.channels["hot"]
old_channel._running = True
def fail_build(*_args, **_kwargs):
raise RuntimeError("invalid replacement config")
monkeypatch.setattr(manager, "_build_channel", fail_build)
result = await manager.apply_channel_feature_action("enable", "hot")
assert result["requires_restart"] is True
assert manager.channels["hot"] is old_channel
assert old_channel.is_running is True
assert not old_channel.stopped.is_set()