- Add plugin discovery via Python entry_points (group: nanobot.channels) - Move 11 channel Config classes from schema.py into their own channel modules - ChannelsConfig now only keeps send_progress + send_tool_hints (extra=allow) - Each built-in channel parses dict->Pydantic in __init__, zero internal changes - All channels implement default_config() for onboard auto-population - nanobot onboard injects defaults for all discovered channels (built-in + plugins) - Add nanobot plugins list CLI command - Add Channel Plugin Guide (docs/CHANNEL_PLUGIN_GUIDE.md) - Fully backward compatible: existing config.json and sessions work as-is - 340 tests pass, zero regressions
91 lines
2.6 KiB
Python
91 lines
2.6 KiB
Python
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from nanobot.bus.events import OutboundMessage
|
|
from nanobot.bus.queue import MessageBus
|
|
from nanobot.channels.slack import SlackChannel
|
|
from nanobot.channels.slack import SlackConfig
|
|
|
|
|
|
class _FakeAsyncWebClient:
|
|
def __init__(self) -> None:
|
|
self.chat_post_calls: list[dict[str, object | None]] = []
|
|
self.file_upload_calls: list[dict[str, object | None]] = []
|
|
|
|
async def chat_postMessage(
|
|
self,
|
|
*,
|
|
channel: str,
|
|
text: str,
|
|
thread_ts: str | None = None,
|
|
) -> None:
|
|
self.chat_post_calls.append(
|
|
{
|
|
"channel": channel,
|
|
"text": text,
|
|
"thread_ts": thread_ts,
|
|
}
|
|
)
|
|
|
|
async def files_upload_v2(
|
|
self,
|
|
*,
|
|
channel: str,
|
|
file: str,
|
|
thread_ts: str | None = None,
|
|
) -> None:
|
|
self.file_upload_calls.append(
|
|
{
|
|
"channel": channel,
|
|
"file": file,
|
|
"thread_ts": thread_ts,
|
|
}
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_send_uses_thread_for_channel_messages() -> None:
|
|
channel = SlackChannel(SlackConfig(enabled=True), MessageBus())
|
|
fake_web = _FakeAsyncWebClient()
|
|
channel._web_client = fake_web
|
|
|
|
await channel.send(
|
|
OutboundMessage(
|
|
channel="slack",
|
|
chat_id="C123",
|
|
content="hello",
|
|
media=["/tmp/demo.txt"],
|
|
metadata={"slack": {"thread_ts": "1700000000.000100", "channel_type": "channel"}},
|
|
)
|
|
)
|
|
|
|
assert len(fake_web.chat_post_calls) == 1
|
|
assert fake_web.chat_post_calls[0]["text"] == "hello\n"
|
|
assert fake_web.chat_post_calls[0]["thread_ts"] == "1700000000.000100"
|
|
assert len(fake_web.file_upload_calls) == 1
|
|
assert fake_web.file_upload_calls[0]["thread_ts"] == "1700000000.000100"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_send_omits_thread_for_dm_messages() -> None:
|
|
channel = SlackChannel(SlackConfig(enabled=True), MessageBus())
|
|
fake_web = _FakeAsyncWebClient()
|
|
channel._web_client = fake_web
|
|
|
|
await channel.send(
|
|
OutboundMessage(
|
|
channel="slack",
|
|
chat_id="D123",
|
|
content="hello",
|
|
media=["/tmp/demo.txt"],
|
|
metadata={"slack": {"thread_ts": "1700000000.000100", "channel_type": "im"}},
|
|
)
|
|
)
|
|
|
|
assert len(fake_web.chat_post_calls) == 1
|
|
assert fake_web.chat_post_calls[0]["text"] == "hello\n"
|
|
assert fake_web.chat_post_calls[0]["thread_ts"] is None
|
|
assert len(fake_web.file_upload_calls) == 1
|
|
assert fake_web.file_upload_calls[0]["thread_ts"] is None
|