from __future__ import annotations from yuxi.channels.adapters.irc._isupport import ISupport from yuxi.channels.adapters.irc.normalizer import _parse_irc_time_tag, normalize_inbound from yuxi.channels.models import ChannelType EXAMPLE_ISUPPORT = ( ":irc.example.com 005 mybot CASEMAPPING=rfc1459 CHANMODES=b,k,l,imnpst " "CHANTYPES=# PREFIX=(ov)@+ NICKLEN=16 CHANNELLEN=50 TOPICLEN=390 " "KICKLEN=400 AWAYLEN=200 MODES=3 MAXLIST=b:60,e:60,I:60 " "STATUSMSG=@+ EXCEPTS=e INVEX=I MONITOR=100 " ":are supported by this server" ) class TestISupport: def test_parse_prefix_map(self): isup = ISupport() isup.feed_line(EXAMPLE_ISUPPORT) assert isup.prefix_map == {"@": "o", "+": "v"} assert isup.mode_to_prefix("o") == "@" assert isup.mode_to_prefix("v") == "+" assert isup.prefix_to_mode("@") == "o" assert isup.prefix_to_mode("+") == "v" def test_parse_casemapping(self): isup = ISupport() isup.feed_line(EXAMPLE_ISUPPORT) assert isup.casemapping == "rfc1459" def test_parse_nicklen(self): isup = ISupport() isup.feed_line(EXAMPLE_ISUPPORT) assert isup.nicklen == 16 def test_parse_chantypes(self): isup = ISupport() isup.feed_line(EXAMPLE_ISUPPORT) assert isup.chantypes == "#" def test_parse_chanmodes(self): isup = ISupport() isup.feed_line(EXAMPLE_ISUPPORT) d = isup.to_dict() assert d["chanmodes"]["a"] == "b" assert d["chanmodes"]["b"] == "k" assert d["chanmodes"]["c"] == "l" assert d["chanmodes"]["d"] == "imnpst" def test_parse_maxlist(self): isup = ISupport() isup.feed_line(EXAMPLE_ISUPPORT) d = isup.to_dict() assert d["has_excepts"] is True assert d["has_invex"] is True def test_parse_monitor(self): isup = ISupport() isup.feed_line(EXAMPLE_ISUPPORT) assert isup.monitor == 100 def test_parse_modes(self): isup = ISupport() isup.feed_line(EXAMPLE_ISUPPORT) assert isup.modes == 3 def test_parse_topiclen(self): isup = ISupport() isup.feed_line(EXAMPLE_ISUPPORT) assert isup.topiclen == 390 def test_parse_kicklen(self): isup = ISupport() isup.feed_line(EXAMPLE_ISUPPORT) assert isup.kicklen == 400 def test_parse_awaylen(self): isup = ISupport() isup.feed_line(EXAMPLE_ISUPPORT) assert isup.awaylen == 200 def test_parse_statusmsg(self): isup = ISupport() isup.feed_line(EXAMPLE_ISUPPORT) assert isup.prefixes == ["@", "+"] def test_strip_prefixes(self): isup = ISupport() isup.feed_line(EXAMPLE_ISUPPORT) assert isup.strip_prefixes("@nick") == "nick" assert isup.strip_prefixes("+nick") == "nick" assert isup.strip_prefixes("nick") == "nick" assert isup.strip_prefixes("") == "" def test_prefix_to_mode_unknown(self): isup = ISupport() isup.feed_line(EXAMPLE_ISUPPORT) assert isup.prefix_to_mode("~") is None def test_mode_to_prefix_unknown(self): isup = ISupport() isup.feed_line(EXAMPLE_ISUPPORT) assert isup.mode_to_prefix("q") is None def test_to_dict_includes_all_sections(self): isup = ISupport() isup.feed_line(EXAMPLE_ISUPPORT) d = isup.to_dict() for key in [ "casemapping", "chantypes", "nicklen", "channellen", "topiclen", "kicklen", "awaylen", "modes", "monitor", "prefix_map", "chanmodes", "has_excepts", "has_invex", "raw", ]: assert key in d, f"Missing key: {key}" def test_get_raw_token(self): isup = ISupport() isup.feed_line(EXAMPLE_ISUPPORT) assert isup.get("NICKLEN") == "16" assert isup.get("NOT_EXISTS", "fallback") == "fallback" def test_feed_multiple_lines(self): isup = ISupport() isup.feed_line(":irc.test 005 bot CASEMAPPING=ascii NICKLEN=9 :first") isup.feed_line(":irc.test 005 bot CHANTYPES=&# CHANNELLEN=32 :second") assert isup.casemapping == "ascii" assert isup.nicklen == 9 assert isup.chantypes == "&#" assert isup.channellen == 32 def test_feed_token_without_value(self): isup = ISupport() isup.feed_line(":irc.test 005 bot SAFELIST :are supported") assert isup.get("SAFELIST") == "" def test_channellen_default(self): isup = ISupport() assert isup.channellen == 200 def test_default_prefix_map(self): isup = ISupport() assert isup.prefix_map == {"@": "o", "+": "v"} class TestServerTimeParsing: def test_parse_valid_iso_time(self): ts = _parse_irc_time_tag("2024-06-15T12:30:45.123Z") assert ts > 0 def test_parse_time_without_ms(self): ts = _parse_irc_time_tag("2024-06-15T12:30:45Z") assert ts > 0 def test_parse_invalid_returns_current(self): import time now = int(time.time() * 1000) ts = _parse_irc_time_tag("not-a-time") assert abs(ts - now) < 1000 def test_parse_empty_returns_current(self): import time now = int(time.time() * 1000) ts = _parse_irc_time_tag("") assert abs(ts - now) < 1000 class TestNormalizeInboundWithServerTime: def test_uses_server_time_when_present(self): raw = { "sender_nick": "user1", "target": "#test", "text": "hello", "time": "2024-06-15T12:00:00.000Z", } msg = normalize_inbound( raw, "irc-channel-id", ChannelType.IRC, "mybot" ) assert msg.identity.channel_message_id.startswith("user1:") assert "1718452800000" in msg.identity.channel_message_id def test_uses_local_time_when_no_time_tag(self): raw = { "sender_nick": "user1", "target": "#test", "text": "hello", } msg = normalize_inbound( raw, "irc-channel-id", ChannelType.IRC, "mybot" ) assert msg.identity.channel_message_id.startswith("user1:") def test_channel_message_mentions(self): raw = { "sender_nick": "user1", "target": "#test", "text": "hello @mybot how are you", } msg = normalize_inbound( raw, "irc-channel-id", ChannelType.IRC, "mybot" ) assert msg.mentions.is_bot_mentioned is True assert "mybot" in msg.mentions.mentioned_user_ids def test_direct_message_normalization(self): raw = { "sender_nick": "alice", "target": "mybot", "text": "hi there", } msg = normalize_inbound( raw, "irc-channel-id", ChannelType.IRC, "mybot" ) assert msg.chat_type.value == "direct" assert msg.identity.channel_chat_id == "dm_alice"