197 lines
6.4 KiB
Python
197 lines
6.4 KiB
Python
import json
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
from typer.testing import CliRunner
|
|
|
|
from nanobot.cli.commands import _resolve_channel_default_config, app
|
|
from nanobot.config.loader import load_config, save_config
|
|
|
|
runner = CliRunner()
|
|
|
|
|
|
def test_load_config_keeps_max_tokens_and_warns_on_legacy_memory_window(tmp_path) -> None:
|
|
config_path = tmp_path / "config.json"
|
|
config_path.write_text(
|
|
json.dumps(
|
|
{
|
|
"agents": {
|
|
"defaults": {
|
|
"maxTokens": 1234,
|
|
"memoryWindow": 42,
|
|
}
|
|
}
|
|
}
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
config = load_config(config_path)
|
|
|
|
assert config.agents.defaults.max_tokens == 1234
|
|
assert config.agents.defaults.context_window_tokens == 65_536
|
|
assert config.agents.defaults.should_warn_deprecated_memory_window is True
|
|
|
|
|
|
def test_save_config_writes_context_window_tokens_but_not_memory_window(tmp_path) -> None:
|
|
config_path = tmp_path / "config.json"
|
|
config_path.write_text(
|
|
json.dumps(
|
|
{
|
|
"agents": {
|
|
"defaults": {
|
|
"maxTokens": 2222,
|
|
"memoryWindow": 30,
|
|
}
|
|
}
|
|
}
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
config = load_config(config_path)
|
|
save_config(config, config_path)
|
|
saved = json.loads(config_path.read_text(encoding="utf-8"))
|
|
defaults = saved["agents"]["defaults"]
|
|
|
|
assert defaults["maxTokens"] == 2222
|
|
assert defaults["contextWindowTokens"] == 65_536
|
|
assert "memoryWindow" not in defaults
|
|
|
|
|
|
def test_onboard_refresh_rewrites_legacy_config_template(tmp_path, monkeypatch) -> None:
|
|
config_path = tmp_path / "config.json"
|
|
workspace = tmp_path / "workspace"
|
|
config_path.write_text(
|
|
json.dumps(
|
|
{
|
|
"agents": {
|
|
"defaults": {
|
|
"maxTokens": 3333,
|
|
"memoryWindow": 50,
|
|
}
|
|
}
|
|
}
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
monkeypatch.setattr("nanobot.config.loader.get_config_path", lambda: config_path)
|
|
monkeypatch.setattr("nanobot.cli.commands.get_workspace_path", lambda _workspace=None: workspace)
|
|
|
|
result = runner.invoke(app, ["onboard"], input="n\n")
|
|
|
|
assert result.exit_code == 0
|
|
assert "contextWindowTokens" in result.stdout
|
|
saved = json.loads(config_path.read_text(encoding="utf-8"))
|
|
defaults = saved["agents"]["defaults"]
|
|
assert defaults["maxTokens"] == 3333
|
|
assert defaults["contextWindowTokens"] == 65_536
|
|
assert "memoryWindow" not in defaults
|
|
|
|
|
|
def test_onboard_refresh_backfills_missing_channel_fields(tmp_path, monkeypatch) -> None:
|
|
config_path = tmp_path / "config.json"
|
|
workspace = tmp_path / "workspace"
|
|
config_path.write_text(
|
|
json.dumps(
|
|
{
|
|
"channels": {
|
|
"qq": {
|
|
"enabled": False,
|
|
"appId": "",
|
|
"secret": "",
|
|
"allowFrom": [],
|
|
}
|
|
}
|
|
}
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
monkeypatch.setattr("nanobot.config.loader.get_config_path", lambda: config_path)
|
|
monkeypatch.setattr("nanobot.cli.commands.get_workspace_path", lambda _workspace=None: workspace)
|
|
monkeypatch.setattr(
|
|
"nanobot.channels.registry.discover_all",
|
|
lambda: {
|
|
"qq": SimpleNamespace(
|
|
default_config=lambda: {
|
|
"enabled": False,
|
|
"appId": "",
|
|
"secret": "",
|
|
"allowFrom": [],
|
|
"msgFormat": "plain",
|
|
}
|
|
)
|
|
},
|
|
)
|
|
|
|
result = runner.invoke(app, ["onboard"], input="n\n")
|
|
|
|
assert result.exit_code == 0
|
|
saved = json.loads(config_path.read_text(encoding="utf-8"))
|
|
assert saved["channels"]["qq"]["msgFormat"] == "plain"
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("channel_cls", "expected"),
|
|
[
|
|
(SimpleNamespace(), None),
|
|
(SimpleNamespace(default_config="invalid"), None),
|
|
(SimpleNamespace(default_config=lambda: None), None),
|
|
(SimpleNamespace(default_config=lambda: ["invalid"]), None),
|
|
(SimpleNamespace(default_config=lambda: {"enabled": False}), {"enabled": False}),
|
|
],
|
|
)
|
|
def test_resolve_channel_default_config_validates_payload(channel_cls, expected) -> None:
|
|
assert _resolve_channel_default_config(channel_cls) == expected
|
|
|
|
|
|
def test_resolve_channel_default_config_skips_exceptions() -> None:
|
|
def _raise() -> dict[str, object]:
|
|
raise RuntimeError("boom")
|
|
|
|
assert _resolve_channel_default_config(SimpleNamespace(default_config=_raise)) is None
|
|
|
|
|
|
def test_onboard_refresh_skips_invalid_channel_default_configs(tmp_path, monkeypatch) -> None:
|
|
config_path = tmp_path / "config.json"
|
|
workspace = tmp_path / "workspace"
|
|
config_path.write_text(json.dumps({"channels": {}}), encoding="utf-8")
|
|
|
|
def _raise() -> dict[str, object]:
|
|
raise RuntimeError("boom")
|
|
|
|
monkeypatch.setattr("nanobot.config.loader.get_config_path", lambda: config_path)
|
|
monkeypatch.setattr("nanobot.cli.commands.get_workspace_path", lambda _workspace=None: workspace)
|
|
monkeypatch.setattr(
|
|
"nanobot.channels.registry.discover_all",
|
|
lambda: {
|
|
"missing": SimpleNamespace(),
|
|
"noncallable": SimpleNamespace(default_config="invalid"),
|
|
"none": SimpleNamespace(default_config=lambda: None),
|
|
"wrong_type": SimpleNamespace(default_config=lambda: ["invalid"]),
|
|
"raises": SimpleNamespace(default_config=_raise),
|
|
"qq": SimpleNamespace(
|
|
default_config=lambda: {
|
|
"enabled": False,
|
|
"appId": "",
|
|
"secret": "",
|
|
"allowFrom": [],
|
|
"msgFormat": "plain",
|
|
}
|
|
),
|
|
},
|
|
)
|
|
|
|
result = runner.invoke(app, ["onboard"], input="n\n")
|
|
|
|
assert result.exit_code == 0
|
|
saved = json.loads(config_path.read_text(encoding="utf-8"))
|
|
assert "missing" not in saved["channels"]
|
|
assert "noncallable" not in saved["channels"]
|
|
assert "none" not in saved["channels"]
|
|
assert "wrong_type" not in saved["channels"]
|
|
assert "raises" not in saved["channels"]
|
|
assert saved["channels"]["qq"]["msgFormat"] == "plain"
|