from __future__ import annotations import pytest from yuxi.channels.adapters.twitch.normalizer import ( normalize_irc_message, resolve_mentions, ) from yuxi.channels.adapters.twitch.irc_parser import ParsedIRCMessage from yuxi.channels.models import ( ChatType, EventType, MessageType, ) def make_parsed(command="PRIVMSG", tags=None, prefix="user!user@host", params=None, trailing=""): return ParsedIRCMessage( command=command, prefix=prefix, params=params or ["#test_channel"], trailing=trailing, tags=tags or {"user-id": "123", "display-name": "TestUser", "tmi-sent-ts": "1600000000000"}, raw="", ) class TestNormalizePrivmsg: def test_basic_message(self): parsed = make_parsed(trailing="Hello world") msg = normalize_irc_message(parsed) assert msg is not None assert msg.content == "Hello world" assert msg.event_type == EventType.MESSAGE_RECEIVED assert msg.message_type == MessageType.TEXT assert msg.chat_type == ChatType.GROUP assert msg.identity.channel_id == "twitch" assert msg.identity.channel_chat_id == "#test_channel" def test_message_with_bits(self): tags = {"user-id": "123", "display-name": "TestUser", "tmi-sent-ts": "1600000000000", "bits": "500"} parsed = make_parsed(tags=tags, trailing="Cheer500") msg = normalize_irc_message(parsed) assert msg is not None assert msg.metadata["bits"] == 500 def test_message_with_mod(self): tags = {"user-id": "123", "display-name": "TestUser", "tmi-sent-ts": "1600000000000", "mod": "1"} parsed = make_parsed(tags=tags, trailing="hello") msg = normalize_irc_message(parsed) assert msg is not None assert msg.metadata["is_mod"] is True def test_message_with_badges(self): tags = { "user-id": "123", "display-name": "TestUser", "tmi-sent-ts": "1600000000000", "badges": "subscriber/6,broadcaster/1", } parsed = make_parsed(tags=tags, trailing="hello") msg = normalize_irc_message(parsed) assert msg is not None assert "subscriber" in msg.metadata["badges"] def test_message_with_display_name(self): tags = {"user-id": "123", "display-name": "CoolUser", "tmi-sent-ts": "1600000000000"} parsed = make_parsed(tags=tags, trailing="hello") msg = normalize_irc_message(parsed) assert msg is not None assert msg.metadata["display_name"] == "CoolUser" class TestNormalizeUsernotice: def test_subscription(self): tags = { "user-id": "456", "display-name": "Subber", "msg-id": "sub", "msg-param-sub-plan": "2000", } parsed = make_parsed(command="USERNOTICE", tags=tags, prefix="subber!subber@host") msg = normalize_irc_message(parsed) assert msg is not None assert msg.event_type == EventType.SYSTEM_EVENT assert msg.metadata["event"] == "subscription" def test_raid(self): tags = { "user-id": "789", "display-name": "Raider", "msg-id": "raid", "msg-param-viewerCount": "42", } parsed = make_parsed(command="USERNOTICE", tags=tags, prefix="raider!raider@host") msg = normalize_irc_message(parsed) assert msg is not None assert msg.metadata["event"] == "raid" assert msg.metadata["msg_param_viewer_count"] == "42" class TestNormalizeClearchat: def test_timeout(self): tags = {"target-user-id": "999", "ban-duration": "300"} parsed = make_parsed(command="CLEARCHAT", tags=tags, trailing="banned_user") msg = normalize_irc_message(parsed) assert msg is not None assert msg.event_type == EventType.MESSAGE_DELETED assert msg.metadata["ban_duration_sec"] == 300 def test_no_target(self): parsed = make_parsed(command="CLEARCHAT", tags={}) msg = normalize_irc_message(parsed) assert msg is not None assert msg.event_type == EventType.MESSAGE_DELETED class TestNormalizeNotice: def test_roomstate_notice_filtered(self): tags = {"msg-id": "slow_on"} parsed = make_parsed(command="NOTICE", tags=tags, trailing="slow mode on") msg = normalize_irc_message(parsed) assert msg is None def test_other_notice(self): tags = {"msg-id": "some_custom_msg"} parsed = make_parsed(command="NOTICE", tags=tags, trailing="custom notice") msg = normalize_irc_message(parsed) assert msg is not None assert msg.event_type == EventType.SYSTEM_EVENT class TestNormalizeSkipCommands: def test_join_skipped(self): parsed = make_parsed(command="JOIN") assert normalize_irc_message(parsed) is None def test_part_skipped(self): parsed = make_parsed(command="PART") assert normalize_irc_message(parsed) is None def test_roomstate_skipped(self): parsed = make_parsed(command="ROOMSTATE") assert normalize_irc_message(parsed) is None def test_ping_skipped(self): parsed = make_parsed(command="PING") assert normalize_irc_message(parsed) is None class TestResolveMentions: def test_single_mention(self): mentions = resolve_mentions("hello @alice") assert "alice" in mentions.mentioned_user_ids def test_multiple_mentions(self): mentions = resolve_mentions("@alice @bob hi") assert "alice" in mentions.mentioned_user_ids assert "bob" in mentions.mentioned_user_ids def test_no_mentions(self): mentions = resolve_mentions("hello world") assert mentions.mentioned_user_ids == [] def test_mention_with_punctuation(self): mentions = resolve_mentions("hey @user, what's up?") assert "user" in mentions.mentioned_user_ids