Add `proxy` config option to server (IRC), teams, telegram, and mumble sections. IRC defaults to false (preserving current direct-connect behavior); all others default to true. The `derp.http` module now accepts `proxy=True/False` on urlopen, create_connection, open_connection, and build_opener -- when false, uses stdlib directly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
167 lines
5.5 KiB
Python
167 lines
5.5 KiB
Python
"""Tests for IRC message parsing and formatting."""
|
|
|
|
from derp.irc import (
|
|
IRCConnection,
|
|
_parse_tags,
|
|
_unescape_tag_value,
|
|
format_msg,
|
|
parse,
|
|
)
|
|
|
|
|
|
class TestParse:
|
|
"""IRC message parser tests."""
|
|
|
|
def test_privmsg(self):
|
|
msg = parse(":nick!user@host PRIVMSG #channel :hello world")
|
|
assert msg.prefix == "nick!user@host"
|
|
assert msg.nick == "nick"
|
|
assert msg.command == "PRIVMSG"
|
|
assert msg.params == ["#channel", "hello world"]
|
|
assert msg.target == "#channel"
|
|
assert msg.text == "hello world"
|
|
assert msg.is_channel is True
|
|
|
|
def test_privmsg_pm(self):
|
|
msg = parse(":nick!user@host PRIVMSG bot :hello")
|
|
assert msg.target == "bot"
|
|
assert msg.is_channel is False
|
|
|
|
def test_ping(self):
|
|
msg = parse("PING :server.example.com")
|
|
assert msg.prefix is None
|
|
assert msg.nick is None
|
|
assert msg.command == "PING"
|
|
assert msg.params == ["server.example.com"]
|
|
|
|
def test_join(self):
|
|
msg = parse(":nick!user@host JOIN #channel")
|
|
assert msg.command == "JOIN"
|
|
assert msg.nick == "nick"
|
|
assert msg.target == "#channel"
|
|
|
|
def test_numeric(self):
|
|
msg = parse(":server 001 bot :Welcome to the IRC Network")
|
|
assert msg.command == "001"
|
|
assert msg.prefix == "server"
|
|
assert msg.nick is None
|
|
assert msg.params == ["bot", "Welcome to the IRC Network"]
|
|
|
|
def test_nick_in_use(self):
|
|
msg = parse(":server 433 * derp :Nickname is already in use")
|
|
assert msg.command == "433"
|
|
assert msg.params[1] == "derp"
|
|
|
|
def test_empty_trailing(self):
|
|
msg = parse(":nick!user@host PRIVMSG #channel :")
|
|
assert msg.text == ""
|
|
|
|
def test_no_prefix(self):
|
|
msg = parse("NOTICE AUTH :*** Looking up your hostname")
|
|
assert msg.prefix is None
|
|
assert msg.command == "NOTICE"
|
|
assert msg.params == ["AUTH", "*** Looking up your hostname"]
|
|
|
|
def test_server_prefix_no_nick(self):
|
|
msg = parse(":irc.server.net 372 bot :- Message of the day")
|
|
assert msg.prefix == "irc.server.net"
|
|
assert msg.nick is None
|
|
|
|
def test_command_case(self):
|
|
msg = parse(":nick!u@h privmsg #ch :test")
|
|
assert msg.command == "PRIVMSG"
|
|
|
|
def test_multiple_colons_in_trailing(self):
|
|
msg = parse(":nick!u@h PRIVMSG #ch :url: https://example.com")
|
|
assert msg.text == "url: https://example.com"
|
|
|
|
def test_part_with_reason(self):
|
|
msg = parse(":nick!u@h PART #channel :leaving")
|
|
assert msg.command == "PART"
|
|
assert msg.target == "#channel"
|
|
assert msg.text == "leaving"
|
|
|
|
def test_no_tags_default(self):
|
|
msg = parse(":nick!u@h PRIVMSG #ch :hello")
|
|
assert msg.tags == {}
|
|
|
|
def test_tags_parsed(self):
|
|
msg = parse("@time=2026-02-15T12:00:00Z;account=alice "
|
|
":nick!user@host PRIVMSG #chan :hello")
|
|
assert msg.tags == {"time": "2026-02-15T12:00:00Z", "account": "alice"}
|
|
assert msg.nick == "nick"
|
|
assert msg.command == "PRIVMSG"
|
|
assert msg.text == "hello"
|
|
|
|
def test_tags_value_unescaping(self):
|
|
msg = parse(r"@key=hello\sworld\:end :nick!u@h PRIVMSG #ch :test")
|
|
assert msg.tags["key"] == "hello world;end"
|
|
|
|
def test_tags_no_value(self):
|
|
msg = parse("@draft/feature :nick!u@h PRIVMSG #ch :test")
|
|
assert msg.tags == {"draft/feature": ""}
|
|
|
|
def test_tags_backslash_escapes(self):
|
|
assert _unescape_tag_value(r"a\\b\r\n") == "a\\b\r\n"
|
|
|
|
def test_tags_mixed_keys(self):
|
|
tags = _parse_tags("a=1;b;c=three")
|
|
assert tags == {"a": "1", "b": "", "c": "three"}
|
|
|
|
def test_tags_empty_string(self):
|
|
assert _parse_tags("") == {}
|
|
|
|
|
|
class TestFormat:
|
|
"""IRC message formatting tests."""
|
|
|
|
def test_simple_command(self):
|
|
assert format_msg("NICK", "derp") == "NICK :derp"
|
|
|
|
def test_command_with_trailing(self):
|
|
assert format_msg("PRIVMSG", "#channel", "hello world") == \
|
|
"PRIVMSG #channel :hello world"
|
|
|
|
def test_join(self):
|
|
assert format_msg("JOIN", "#channel") == "JOIN :#channel"
|
|
|
|
def test_no_params(self):
|
|
assert format_msg("QUIT") == "QUIT"
|
|
|
|
def test_user_registration(self):
|
|
result = format_msg("USER", "derp", "0", "*", "derp IRC bot")
|
|
assert result == "USER derp 0 * :derp IRC bot"
|
|
|
|
def test_pong(self):
|
|
assert format_msg("PONG", "server.example.com") == "PONG :server.example.com"
|
|
|
|
def test_trailing_starts_with_colon(self):
|
|
result = format_msg("PRIVMSG", "#ch", ":)")
|
|
assert result == "PRIVMSG #ch ::)"
|
|
|
|
def test_empty_trailing(self):
|
|
# Empty string has no space and no leading colon, but head exists
|
|
result = format_msg("PRIVMSG", "#ch", "")
|
|
assert result == "PRIVMSG #ch "
|
|
|
|
def test_multi_param_mode(self):
|
|
# No space in tail, not starting with colon, head exists -> no colon
|
|
result = format_msg("MODE", "#ch", "+o", "nick")
|
|
assert result == "MODE #ch +o nick"
|
|
|
|
|
|
class TestIRCConnectionProxy:
|
|
"""IRCConnection proxy flag tests."""
|
|
|
|
def test_proxy_default_false(self):
|
|
conn = IRCConnection("irc.example.com", 6697)
|
|
assert conn.proxy is False
|
|
|
|
def test_proxy_enabled(self):
|
|
conn = IRCConnection("irc.example.com", 6697, proxy=True)
|
|
assert conn.proxy is True
|
|
|
|
def test_proxy_disabled(self):
|
|
conn = IRCConnection("irc.example.com", 6697, proxy=False)
|
|
assert conn.proxy is False
|