test: add config, format_msg, and Bot API tests
New test_config.py: merge, load, resolve_config tests. Extend test_irc.py: format_msg edge cases (colon, empty, multi-param). Extend test_plugin.py: Bot API via FakeConnection, _split_utf8 tests. Test count: 92 -> 120. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
"""Tests for the plugin system."""
|
||||
|
||||
import asyncio
|
||||
import textwrap
|
||||
from pathlib import Path
|
||||
|
||||
from derp.bot import _AMBIGUOUS, Bot
|
||||
from derp.bot import _AMBIGUOUS, Bot, _split_utf8
|
||||
from derp.irc import Message
|
||||
from derp.plugin import PluginRegistry, command, event
|
||||
from derp.state import StateStore
|
||||
@@ -545,3 +546,110 @@ class TestStateStore:
|
||||
store.close()
|
||||
store2 = StateStore(db_path)
|
||||
assert store2.get("plug", "persist") == "yes"
|
||||
|
||||
|
||||
class _FakeConnection:
|
||||
"""Minimal IRCConnection stand-in that captures sent lines."""
|
||||
|
||||
def __init__(self):
|
||||
self.sent: list[str] = []
|
||||
|
||||
async def send(self, line: str) -> None:
|
||||
self.sent.append(line)
|
||||
|
||||
|
||||
def _make_test_bot() -> Bot:
|
||||
"""Create a Bot with a FakeConnection for testing the public API."""
|
||||
config = {
|
||||
"server": {"host": "localhost", "port": 6667, "tls": False,
|
||||
"nick": "test", "user": "test", "realname": "test"},
|
||||
"bot": {"prefix": "!", "channels": [], "plugins_dir": "plugins"},
|
||||
}
|
||||
bot = Bot(config, PluginRegistry())
|
||||
bot.conn = _FakeConnection() # type: ignore[assignment]
|
||||
return bot
|
||||
|
||||
|
||||
class TestBotAPI:
|
||||
"""Test Bot public API methods via FakeConnection."""
|
||||
|
||||
def test_send(self):
|
||||
bot = _make_test_bot()
|
||||
asyncio.run(bot.send("#ch", "hello world"))
|
||||
assert bot.conn.sent == ["PRIVMSG #ch :hello world"]
|
||||
|
||||
def test_send_single_word(self):
|
||||
bot = _make_test_bot()
|
||||
asyncio.run(bot.send("#ch", "hello"))
|
||||
assert bot.conn.sent == ["PRIVMSG #ch hello"]
|
||||
|
||||
def test_send_multiline(self):
|
||||
bot = _make_test_bot()
|
||||
asyncio.run(bot.send("#ch", "line one\nline two"))
|
||||
assert len(bot.conn.sent) == 2
|
||||
assert bot.conn.sent[0] == "PRIVMSG #ch :line one"
|
||||
assert bot.conn.sent[1] == "PRIVMSG #ch :line two"
|
||||
|
||||
def test_reply_channel(self):
|
||||
bot = _make_test_bot()
|
||||
msg = Message(raw="", prefix="nick!u@h", nick="nick",
|
||||
command="PRIVMSG", params=["#ch", "hi"], tags={})
|
||||
asyncio.run(bot.reply(msg, "hello there"))
|
||||
assert bot.conn.sent == ["PRIVMSG #ch :hello there"]
|
||||
|
||||
def test_reply_pm(self):
|
||||
bot = _make_test_bot()
|
||||
msg = Message(raw="", prefix="nick!u@h", nick="nick",
|
||||
command="PRIVMSG", params=["testbot", "hi"], tags={})
|
||||
asyncio.run(bot.reply(msg, "hello there"))
|
||||
assert bot.conn.sent == ["PRIVMSG nick :hello there"]
|
||||
|
||||
def test_join(self):
|
||||
bot = _make_test_bot()
|
||||
asyncio.run(bot.join("#test"))
|
||||
assert bot.conn.sent == ["JOIN :#test"]
|
||||
|
||||
def test_kick(self):
|
||||
bot = _make_test_bot()
|
||||
asyncio.run(bot.kick("#ch", "baduser", "bad behavior"))
|
||||
assert bot.conn.sent == ["KICK #ch baduser :bad behavior"]
|
||||
|
||||
def test_mode(self):
|
||||
bot = _make_test_bot()
|
||||
asyncio.run(bot.mode("#ch", "+o", "nick"))
|
||||
assert bot.conn.sent == ["MODE #ch +o nick"]
|
||||
|
||||
def test_set_topic(self):
|
||||
bot = _make_test_bot()
|
||||
asyncio.run(bot.set_topic("#ch", "new topic"))
|
||||
assert bot.conn.sent == ["TOPIC #ch :new topic"]
|
||||
|
||||
|
||||
class TestSplitUtf8:
|
||||
"""Test UTF-8 safe message splitting."""
|
||||
|
||||
def test_short_text(self):
|
||||
assert _split_utf8("hello", 100) == ["hello"]
|
||||
|
||||
def test_ascii_split(self):
|
||||
text = "a" * 20
|
||||
chunks = _split_utf8(text, 10)
|
||||
assert len(chunks) == 2
|
||||
assert all(len(c.encode("utf-8")) <= 10 for c in chunks)
|
||||
assert "".join(chunks) == text
|
||||
|
||||
def test_multibyte_boundary(self):
|
||||
# Each char is 3 bytes in UTF-8
|
||||
text = "\u00e9" * 10 # e-acute, 2 bytes each -> 20 bytes total
|
||||
chunks = _split_utf8(text, 7)
|
||||
recombined = "".join(chunks)
|
||||
assert recombined == text
|
||||
for chunk in chunks:
|
||||
assert len(chunk.encode("utf-8")) <= 7
|
||||
|
||||
def test_empty(self):
|
||||
assert _split_utf8("", 10) == [""]
|
||||
|
||||
def test_exact_fit(self):
|
||||
text = "abcde"
|
||||
assert _split_utf8(text, 5) == ["abcde"]
|
||||
|
||||
Reference in New Issue
Block a user